Search in sources :

Example 1 with RBFSpend

use of com.samourai.wallet.send.RBFSpend in project samourai-wallet-android by Samourai-Wallet.

the class BatchSendActivity method doSpend.

private void doSpend() {
    final HashMap<String, BigInteger> receivers = new HashMap<String, BigInteger>();
    long amount = 0L;
    for (BatchSendUtil.BatchSend _data : data) {
        Log.d("BatchSendActivity", "output:" + _data.amount);
        Log.d("BatchSendActivity", "output:" + _data.addr);
        Log.d("BatchSendActivity", "output:" + _data.pcode);
        amount += _data.amount;
        if (receivers.containsKey(_data.addr)) {
            BigInteger _amount = receivers.get(_data.addr);
            receivers.put(_data.addr, _amount.add(BigInteger.valueOf(_data.amount)));
        } else {
            receivers.put(_data.addr, BigInteger.valueOf(_data.amount));
        }
    }
    Log.d("BatchSendActivity", "amount:" + amount);
    List<UTXO> utxos = APIFactory.getInstance(BatchSendActivity.this).getUtxos(true);
    Collections.sort(utxos, new UTXO.UTXOComparator());
    List<UTXO> selectedUTXO = new ArrayList<UTXO>();
    int p2pkh = 0;
    int p2sh_p2wpkh = 0;
    int p2wpkh = 0;
    long totalValueSelected = 0L;
    int totalSelected = 0;
    for (UTXO utxo : utxos) {
        Log.d("BatchSendActivity", "utxo value:" + utxo.getValue());
        selectedUTXO.add(utxo);
        totalValueSelected += utxo.getValue();
        totalSelected += utxo.getOutpoints().size();
        Triple<Integer, Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(new Vector(utxo.getOutpoints()));
        p2pkh += outpointTypes.getLeft();
        p2sh_p2wpkh += outpointTypes.getMiddle();
        p2wpkh += outpointTypes.getRight();
        if (totalValueSelected >= (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFeeSegwit(p2pkh, p2sh_p2wpkh, p2wpkh, receivers.size() + 1).longValue())) {
            break;
        }
    }
    Log.d("BatchSendActivity", "totalSelected:" + totalSelected);
    Log.d("BatchSendActivity", "totalValueSelected:" + totalValueSelected);
    final List<MyTransactionOutPoint> outpoints = new ArrayList<MyTransactionOutPoint>();
    for (UTXO utxo : selectedUTXO) {
        outpoints.addAll(utxo.getOutpoints());
        for (MyTransactionOutPoint out : utxo.getOutpoints()) {
            Log.d("BatchSendActivity", "outpoint hash:" + out.getTxHash().toString());
            Log.d("BatchSendActivity", "outpoint idx:" + out.getTxOutputN());
            Log.d("BatchSendActivity", "outpoint address:" + out.getAddress());
        }
    }
    Triple<Integer, Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(new Vector(outpoints));
    BigInteger fee = FeeUtil.getInstance().estimatedFeeSegwit(outpointTypes.getLeft(), outpointTypes.getMiddle(), outpointTypes.getRight(), receivers.size() + 1);
    Log.d("BatchSendActivity", "fee:" + fee.longValue());
    if (amount + fee.longValue() > balance) {
        Toast.makeText(BatchSendActivity.this, R.string.insufficient_funds, Toast.LENGTH_SHORT).show();
        return;
    }
    long changeAmount = totalValueSelected - (amount + fee.longValue());
    String change_address = null;
    int change_idx = 0;
    if (changeAmount > 0L) {
        change_idx = BIP84Util.getInstance(BatchSendActivity.this).getWallet().getAccount(0).getChange().getAddrIdx();
        change_address = BIP84Util.getInstance(BatchSendActivity.this).getAddressAt(AddressFactory.CHANGE_CHAIN, change_idx).getBech32AsString();
        receivers.put(change_address, BigInteger.valueOf(changeAmount));
        Log.d("BatchSendActivity", "change output:" + changeAmount);
        Log.d("BatchSendActivity", "change output:" + change_address);
    }
    Transaction tx = SendFactory.getInstance(BatchSendActivity.this).makeTransaction(0, outpoints, receivers);
    if (tx != null) {
        final RBFSpend rbf;
        if (PrefsUtil.getInstance(BatchSendActivity.this).getValue(PrefsUtil.RBF_OPT_IN, false) == true) {
            rbf = new RBFSpend();
            for (TransactionInput input : tx.getInputs()) {
                boolean _isBIP49 = false;
                boolean _isBIP84 = false;
                String _addr = null;
                String script = Hex.toHexString(input.getConnectedOutput().getScriptBytes());
                if (Bech32Util.getInstance().isBech32Script(script)) {
                    try {
                        _addr = Bech32Util.getInstance().getAddressFromScript(script);
                        _isBIP84 = true;
                    } catch (Exception e) {
                        ;
                    }
                } else {
                    Address _address = input.getConnectedOutput().getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams());
                    if (_address != null) {
                        _addr = _address.toString();
                        _isBIP49 = true;
                    }
                }
                if (_addr == null) {
                    _addr = input.getConnectedOutput().getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
                }
                String path = APIFactory.getInstance(BatchSendActivity.this).getUnspentPaths().get(_addr);
                if (path != null) {
                    if (_isBIP84) {
                        rbf.addKey(input.getOutpoint().toString(), path + "/84");
                    } else if (_isBIP49) {
                        rbf.addKey(input.getOutpoint().toString(), path + "/49");
                    } else {
                        rbf.addKey(input.getOutpoint().toString(), path);
                    }
                } else {
                    String pcode = BIP47Meta.getInstance().getPCode4Addr(_addr);
                    int idx = BIP47Meta.getInstance().getIdx4Addr(_addr);
                    rbf.addKey(input.getOutpoint().toString(), pcode + "/" + idx);
                }
            }
        } else {
            rbf = null;
        }
        String strChangeIsDust = "";
        String strPrivacyWarning = "";
        String strMessage = strChangeIsDust + strPrivacyWarning + "Send " + Coin.valueOf(amount).toPlainString() + " BTC. (fee:" + Coin.valueOf(fee.longValue()).toPlainString() + ")?\n";
        final long _change = changeAmount;
        final String _change_address = change_address;
        final int _change_idx = change_idx;
        final long _amount = amount;
        tx = SendFactory.getInstance(BatchSendActivity.this).signTransaction(tx, 0);
        final String hexTx = new String(Hex.encode(tx.bitcoinSerialize()));
        final String strTxHash = tx.getHashAsString();
        // Log.d("BatchSendActivity", "tx hash:" + tx.getHashAsString());
        // Log.d("BatchSendActivity", "hex signed tx:" + hexTx);
        AlertDialog.Builder dlg = new AlertDialog.Builder(BatchSendActivity.this).setTitle(R.string.app_name).setMessage(strMessage).setCancelable(false).setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {

            public void onClick(final DialogInterface dialog, int whichButton) {
                dialog.dismiss();
                if (PrefsUtil.getInstance(BatchSendActivity.this).getValue(PrefsUtil.BROADCAST_TX, true) == false) {
                    doShowTx(hexTx, strTxHash);
                    return;
                }
                SendParams.getInstance().setParams(outpoints, receivers, data, SendActivity.SPEND_SIMPLE, _change, 84, 0, "", false, false, _amount, _change_idx);
                Intent _intent = new Intent(BatchSendActivity.this, TxAnimUIActivity.class);
                startActivity(_intent);
            }
        }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int whichButton) {
                dialog.dismiss();
            }
        });
        if (!isFinishing()) {
            dlg.show();
        }
    } else {
        // Log.d("SendActivity", "tx error");
        Toast.makeText(BatchSendActivity.this, "tx error", Toast.LENGTH_SHORT).show();
    }
}
Also used : AlertDialog(android.app.AlertDialog) Address(org.bitcoinj.core.Address) PaymentAddress(com.samourai.wallet.bip47.rpc.PaymentAddress) SegwitAddress(com.samourai.wallet.segwit.SegwitAddress) HashMap(java.util.HashMap) BatchSendUtil(com.samourai.wallet.util.BatchSendUtil) DialogInterface(android.content.DialogInterface) ArrayList(java.util.ArrayList) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) TransactionInput(org.bitcoinj.core.TransactionInput) RBFSpend(com.samourai.wallet.send.RBFSpend) Vector(java.util.Vector) Intent(android.content.Intent) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) Point(android.graphics.Point) WriterException(com.google.zxing.WriterException) ParseException(java.text.ParseException) FileNotFoundException(java.io.FileNotFoundException) UnsupportedEncodingException(java.io.UnsupportedEncodingException) MnemonicException(org.bitcoinj.crypto.MnemonicException) IOException(java.io.IOException) BigInteger(java.math.BigInteger) UTXO(com.samourai.wallet.send.UTXO) Transaction(org.bitcoinj.core.Transaction) BigInteger(java.math.BigInteger)

Example 2 with RBFSpend

use of com.samourai.wallet.send.RBFSpend in project samourai-wallet-android by Samourai-Wallet.

the class TxAnimUIActivity method onCreate.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_tx_anim_ui);
    progressView = findViewById(R.id.transactionProgressView);
    progressView.reset();
    progressView.setTxStatusMessage(R.string.tx_creating_ok);
    progressView.getmArcProgress().startArc1(arcdelay);
    // make tx
    final Transaction tx = SendFactory.getInstance(TxAnimUIActivity.this).makeTransaction(SendParams.getInstance().getAccount(), SendParams.getInstance().getOutpoints(), SendParams.getInstance().getReceivers());
    if (tx == null) {
        failTx(R.string.tx_creating_ko);
    } else {
        // Toast.makeText(TxAnimUIActivity.this, "tx created OK", Toast.LENGTH_SHORT).show();
        final RBFSpend rbf;
        if (PrefsUtil.getInstance(TxAnimUIActivity.this).getValue(PrefsUtil.RBF_OPT_IN, false) == true) {
            rbf = new RBFSpend();
            for (TransactionInput input : tx.getInputs()) {
                boolean _isBIP49 = false;
                boolean _isBIP84 = false;
                String _addr = null;
                String script = Hex.toHexString(input.getConnectedOutput().getScriptBytes());
                if (Bech32Util.getInstance().isBech32Script(script)) {
                    try {
                        _addr = Bech32Util.getInstance().getAddressFromScript(script);
                        _isBIP84 = true;
                    } catch (Exception e) {
                        ;
                    }
                } else {
                    Address _address = input.getConnectedOutput().getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams());
                    if (_address != null) {
                        _addr = _address.toString();
                        _isBIP49 = true;
                    }
                }
                if (_addr == null) {
                    _addr = input.getConnectedOutput().getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
                }
                String path = APIFactory.getInstance(TxAnimUIActivity.this).getUnspentPaths().get(_addr);
                if (path != null) {
                    if (_isBIP84) {
                        rbf.addKey(input.getOutpoint().toString(), path + "/84");
                    } else if (_isBIP49) {
                        rbf.addKey(input.getOutpoint().toString(), path + "/49");
                    } else {
                        rbf.addKey(input.getOutpoint().toString(), path);
                    }
                } else {
                    String pcode = BIP47Meta.getInstance().getPCode4Addr(_addr);
                    int idx = BIP47Meta.getInstance().getIdx4Addr(_addr);
                    rbf.addKey(input.getOutpoint().toString(), pcode + "/" + idx);
                }
            }
        } else {
            rbf = null;
        }
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                TxAnimUIActivity.this.runOnUiThread(() -> {
                    progressView.getmArcProgress().startArc2(arcdelay);
                    progressView.setTxStatusMessage(R.string.tx_signing_ok);
                });
                final Transaction _tx = SendFactory.getInstance(TxAnimUIActivity.this).signTransaction(tx, SendParams.getInstance().getAccount());
                if (_tx == null) {
                    failTx(R.string.tx_signing_ko);
                } else {
                // Toast.makeText(TxAnimUIActivity.this, "tx signed OK", Toast.LENGTH_SHORT).show();
                }
                final String hexTx = new String(Hex.encode(_tx.bitcoinSerialize()));
                Log.d("TxAnimUIActivity", "hex tx:" + hexTx);
                final String strTxHash = _tx.getHashAsString();
                resultHandler = new Handler();
                new Handler().postDelayed(new Runnable() {

                    @Override
                    public void run() {
                        TxAnimUIActivity.this.runOnUiThread(() -> {
                            progressView.getmArcProgress().startArc3(arcdelay);
                            progressView.setTxStatusMessage(R.string.tx_broadcast_ok);
                        });
                        if (PrefsUtil.getInstance(TxAnimUIActivity.this).getValue(PrefsUtil.BROADCAST_TX, true) == false) {
                            offlineTx(R.string.broadcast_off, hexTx, strTxHash);
                            return;
                        }
                        if (AppUtil.getInstance(TxAnimUIActivity.this).isOfflineMode()) {
                            offlineTx(R.string.offline_mode, hexTx, strTxHash);
                            return;
                        }
                        new Thread(new Runnable() {

                            @Override
                            public void run() {
                                Looper.prepare();
                                boolean isOK = false;
                                String response = PushTx.getInstance(TxAnimUIActivity.this).samourai(hexTx);
                                try {
                                    if (PrefsUtil.getInstance(TxAnimUIActivity.this).getValue(PrefsUtil.USE_TRUSTED_NODE, false) == true) {
                                        if (TrustedNodeUtil.getInstance().isSet()) {
                                            response = PushTx.getInstance(TxAnimUIActivity.this).trustedNode(hexTx);
                                            JSONObject jsonObject = new org.json.JSONObject(response);
                                            if (jsonObject.has("result")) {
                                                if (jsonObject.getString("result").matches("^[A-Za-z0-9]{64}$")) {
                                                    isOK = true;
                                                } else {
                                                    Toast.makeText(TxAnimUIActivity.this, R.string.trusted_node_tx_error, Toast.LENGTH_SHORT).show();
                                                    failTx(R.string.tx_broadcast_ko);
                                                }
                                            }
                                        } else {
                                            Toast.makeText(TxAnimUIActivity.this, R.string.trusted_node_not_valid, Toast.LENGTH_SHORT).show();
                                            failTx(R.string.tx_broadcast_ko);
                                        }
                                    } else {
                                        if (response != null) {
                                            JSONObject jsonObject = new org.json.JSONObject(response);
                                            if (jsonObject.has("status")) {
                                                if (jsonObject.getString("status").equals("ok")) {
                                                    isOK = true;
                                                }
                                            }
                                        } else {
                                            Toast.makeText(TxAnimUIActivity.this, R.string.pushtx_returns_null, Toast.LENGTH_SHORT).show();
                                            failTx(R.string.tx_broadcast_ko);
                                        }
                                    }
                                } catch (JSONException je) {
                                    failTx(R.string.tx_broadcast_ko);
                                }
                                final boolean _isOK = isOK;
                                resultHandler.postDelayed(new Runnable() {

                                    @Override
                                    public void run() {
                                        if (_isOK) {
                                            progressView.showCheck();
                                            progressView.setTxStatusMessage(R.string.tx_sent_ok);
                                        } else {
                                            failTx(R.string.tx_sent_ko);
                                        }
                                        handleResult(_isOK, rbf, strTxHash, hexTx, _tx);
                                    }
                                }, resultDelay);
                                Looper.loop();
                            }
                        }).start();
                    }
                }, broadcastDelay);
            }
        }, signDelay);
    }
}
Also used : Address(org.bitcoinj.core.Address) Handler(android.os.Handler) JSONException(org.json.JSONException) TransactionInput(org.bitcoinj.core.TransactionInput) WriterException(com.google.zxing.WriterException) JSONException(org.json.JSONException) DecoderException(org.spongycastle.util.encoders.DecoderException) FileNotFoundException(java.io.FileNotFoundException) MnemonicException(org.bitcoinj.crypto.MnemonicException) IOException(java.io.IOException) Point(android.graphics.Point) Transaction(org.bitcoinj.core.Transaction) JSONObject(org.json.JSONObject) RBFSpend(com.samourai.wallet.send.RBFSpend)

Example 3 with RBFSpend

use of com.samourai.wallet.send.RBFSpend in project samourai-wallet-android by Samourai-Wallet.

the class SendActivity method onCreate.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_send);
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    SendActivity.this.getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
    if (SamouraiWallet.getInstance().getShowTotalBalance()) {
        if (SamouraiWallet.getInstance().getCurrentSelectedAccount() == 2) {
            selectedAccount = 1;
        } else {
            selectedAccount = 0;
        }
    } else {
        selectedAccount = 0;
    }
    tvMaxPrompt = (TextView) findViewById(R.id.max_prompt);
    tvMax = (TextView) findViewById(R.id.max);
    try {
        balance = APIFactory.getInstance(SendActivity.this).getXpubAmounts().get(HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(selectedAccount).xpubstr());
    } catch (IOException ioe) {
        balance = 0L;
    } catch (MnemonicException.MnemonicLengthException mle) {
        balance = 0L;
    } catch (java.lang.NullPointerException npe) {
        balance = 0L;
    }
    final String strAmount;
    NumberFormat nf = NumberFormat.getInstance(Locale.US);
    nf.setMaximumFractionDigits(8);
    nf.setMinimumFractionDigits(1);
    nf.setMinimumIntegerDigits(1);
    strAmount = nf.format(balance / 1e8);
    tvMax.setText(strAmount + " " + getDisplayUnits());
    tvMaxPrompt.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            edAmountBTC.setText(strAmount);
            return false;
        }
    });
    tvMax.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            edAmountBTC.setText(strAmount);
            return false;
        }
    });
    DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance(Locale.US);
    DecimalFormatSymbols symbols = format.getDecimalFormatSymbols();
    defaultSeparator = Character.toString(symbols.getDecimalSeparator());
    strFiat = PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.CURRENT_FIAT, "USD");
    btc_fx = ExchangeRateFactory.getInstance(SendActivity.this).getAvgPrice(strFiat);
    tvFiatSymbol = (TextView) findViewById(R.id.fiatSymbol);
    tvFiatSymbol.setText(getDisplayUnits() + "-" + strFiat);
    edAddress = (EditText) findViewById(R.id.destination);
    textWatcherAddress = new TextWatcher() {

        public void afterTextChanged(Editable s) {
            validateSpend();
        }

        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            ;
        }

        public void onTextChanged(CharSequence s, int start, int before, int count) {
            ;
        }
    };
    edAddress.addTextChangedListener(textWatcherAddress);
    edAddress.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // final int DRAWABLE_LEFT = 0;
            // final int DRAWABLE_TOP = 1;
            final int DRAWABLE_RIGHT = 2;
            if (event.getAction() == MotionEvent.ACTION_UP && event.getRawX() >= (edAddress.getRight() - edAddress.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width())) {
                final List<String> entries = new ArrayList<String>();
                entries.addAll(BIP47Meta.getInstance().getSortedByLabels(false));
                final ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(SendActivity.this, android.R.layout.select_dialog_singlechoice);
                for (int i = 0; i < entries.size(); i++) {
                    arrayAdapter.add(BIP47Meta.getInstance().getDisplayLabel(entries.get(i)));
                }
                AlertDialog.Builder dlg = new AlertDialog.Builder(SendActivity.this);
                dlg.setIcon(R.drawable.ic_launcher);
                dlg.setTitle(R.string.app_name);
                dlg.setAdapter(arrayAdapter, new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // Toast.makeText(SendActivity.this, BIP47Meta.getInstance().getDisplayLabel(entries.get(which)), Toast.LENGTH_SHORT).show();
                        // Toast.makeText(SendActivity.this, entries.get(which), Toast.LENGTH_SHORT).show();
                        processPCode(entries.get(which), null);
                    }
                });
                dlg.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });
                dlg.show();
                return true;
            }
            return false;
        }
    });
    edAmountBTC = (EditText) findViewById(R.id.amountBTC);
    edAmountFiat = (EditText) findViewById(R.id.amountFiat);
    textWatcherBTC = new TextWatcher() {

        public void afterTextChanged(Editable s) {
            edAmountBTC.removeTextChangedListener(this);
            edAmountFiat.removeTextChangedListener(textWatcherFiat);
            int max_len = 8;
            NumberFormat btcFormat = NumberFormat.getInstance(Locale.US);
            btcFormat.setMaximumFractionDigits(max_len + 1);
            btcFormat.setMinimumFractionDigits(0);
            double d = 0.0;
            try {
                d = NumberFormat.getInstance(Locale.US).parse(s.toString()).doubleValue();
                String s1 = btcFormat.format(d);
                if (s1.indexOf(defaultSeparator) != -1) {
                    String dec = s1.substring(s1.indexOf(defaultSeparator));
                    if (dec.length() > 0) {
                        dec = dec.substring(1);
                        if (dec.length() > max_len) {
                            edAmountBTC.setText(s1.substring(0, s1.length() - 1));
                            edAmountBTC.setSelection(edAmountBTC.getText().length());
                            s = edAmountBTC.getEditableText();
                        }
                    }
                }
            } catch (NumberFormatException nfe) {
                ;
            } catch (ParseException pe) {
                ;
            }
            if (d > 21000000.0) {
                edAmountFiat.setText("0.00");
                edAmountFiat.setSelection(edAmountFiat.getText().length());
                edAmountBTC.setText("0");
                edAmountBTC.setSelection(edAmountBTC.getText().length());
                Toast.makeText(SendActivity.this, R.string.invalid_amount, Toast.LENGTH_SHORT).show();
            } else {
                edAmountFiat.setText(MonetaryUtil.getInstance().getFiatFormat(strFiat).format(d * btc_fx));
                edAmountFiat.setSelection(edAmountFiat.getText().length());
            }
            edAmountFiat.addTextChangedListener(textWatcherFiat);
            edAmountBTC.addTextChangedListener(this);
            validateSpend();
        }

        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            ;
        }

        public void onTextChanged(CharSequence s, int start, int before, int count) {
            ;
        }
    };
    edAmountBTC.addTextChangedListener(textWatcherBTC);
    textWatcherFiat = new TextWatcher() {

        public void afterTextChanged(Editable s) {
            edAmountFiat.removeTextChangedListener(this);
            edAmountBTC.removeTextChangedListener(textWatcherBTC);
            int max_len = 2;
            NumberFormat fiatFormat = NumberFormat.getInstance(Locale.US);
            fiatFormat.setMaximumFractionDigits(max_len + 1);
            fiatFormat.setMinimumFractionDigits(0);
            double d = 0.0;
            try {
                d = NumberFormat.getInstance(Locale.US).parse(s.toString()).doubleValue();
                String s1 = fiatFormat.format(d);
                if (s1.indexOf(defaultSeparator) != -1) {
                    String dec = s1.substring(s1.indexOf(defaultSeparator));
                    if (dec.length() > 0) {
                        dec = dec.substring(1);
                        if (dec.length() > max_len) {
                            edAmountFiat.setText(s1.substring(0, s1.length() - 1));
                            edAmountFiat.setSelection(edAmountFiat.getText().length());
                        }
                    }
                }
            } catch (NumberFormatException nfe) {
                ;
            } catch (ParseException pe) {
                ;
            }
            if ((d / btc_fx) > 21000000.0) {
                edAmountFiat.setText("0.00");
                edAmountFiat.setSelection(edAmountFiat.getText().length());
                edAmountBTC.setText("0");
                edAmountBTC.setSelection(edAmountBTC.getText().length());
                Toast.makeText(SendActivity.this, R.string.invalid_amount, Toast.LENGTH_SHORT).show();
            } else {
                edAmountBTC.setText(MonetaryUtil.getInstance().getBTCFormat().format(d / btc_fx));
                edAmountBTC.setSelection(edAmountBTC.getText().length());
            }
            edAmountBTC.addTextChangedListener(textWatcherBTC);
            edAmountFiat.addTextChangedListener(this);
            validateSpend();
        }

        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            ;
        }

        public void onTextChanged(CharSequence s, int start, int before, int count) {
            ;
        }
    };
    edAmountFiat.addTextChangedListener(textWatcherFiat);
    SPEND_TYPE = PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.USE_BIP126, true) ? SPEND_BIP126 : SPEND_SIMPLE;
    if (SPEND_TYPE > SPEND_BIP126) {
        SPEND_TYPE = SPEND_BIP126;
        PrefsUtil.getInstance(SendActivity.this).setValue(PrefsUtil.SPEND_TYPE, SPEND_BIP126);
    }
    swRicochet = (Switch) findViewById(R.id.ricochet);
    swRicochet.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (isChecked) {
                SPEND_TYPE = SPEND_RICOCHET;
            } else {
                SPEND_TYPE = PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.SPEND_TYPE, SPEND_BIP126);
            }
        }
    });
    btLowFee = (Button) findViewById(R.id.low_fee);
    btAutoFee = (Button) findViewById(R.id.auto_fee);
    btPriorityFee = (Button) findViewById(R.id.priority_fee);
    btCustomFee = (Button) findViewById(R.id.custom_fee);
    tvFeePrompt = (TextView) findViewById(R.id.current_fee_prompt);
    FEE_TYPE = PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.CURRENT_FEE_TYPE, FEE_NORMAL);
    long lo = FeeUtil.getInstance().getLowFee().getDefaultPerKB().longValue() / 1000L;
    long mi = FeeUtil.getInstance().getNormalFee().getDefaultPerKB().longValue() / 1000L;
    long hi = FeeUtil.getInstance().getHighFee().getDefaultPerKB().longValue() / 1000L;
    if (lo == mi && mi == hi) {
        lo = (long) ((double) mi * 0.85);
        hi = (long) ((double) mi * 1.15);
        SuggestedFee lo_sf = new SuggestedFee();
        lo_sf.setDefaultPerKB(BigInteger.valueOf(lo * 1000L));
        FeeUtil.getInstance().setLowFee(lo_sf);
        SuggestedFee hi_sf = new SuggestedFee();
        hi_sf.setDefaultPerKB(BigInteger.valueOf(hi * 1000L));
        FeeUtil.getInstance().setHighFee(hi_sf);
    } else if (lo == mi || mi == hi) {
        mi = (lo + hi) / 2L;
        SuggestedFee mi_sf = new SuggestedFee();
        mi_sf.setDefaultPerKB(BigInteger.valueOf(mi * 1000L));
        FeeUtil.getInstance().setNormalFee(mi_sf);
    } else {
        ;
    }
    switch(FEE_TYPE) {
        case FEE_LOW:
            FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getLowFee());
            btLowFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.blue));
            btAutoFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btPriorityFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btCustomFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btLowFee.setTypeface(null, Typeface.BOLD);
            btAutoFee.setTypeface(null, Typeface.NORMAL);
            btPriorityFee.setTypeface(null, Typeface.NORMAL);
            btCustomFee.setTypeface(null, Typeface.NORMAL);
            tvFeePrompt.setText(getText(R.string.fee_low_priority) + " " + getText(R.string.blocks_to_cf));
            break;
        case FEE_PRIORITY:
            FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getHighFee());
            btLowFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btAutoFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btPriorityFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.blue));
            btCustomFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btLowFee.setTypeface(null, Typeface.NORMAL);
            btAutoFee.setTypeface(null, Typeface.NORMAL);
            btPriorityFee.setTypeface(null, Typeface.BOLD);
            btCustomFee.setTypeface(null, Typeface.NORMAL);
            tvFeePrompt.setText(getText(R.string.fee_high_priority) + " " + getText(R.string.blocks_to_cf));
            break;
        default:
            FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getNormalFee());
            btLowFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btAutoFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.blue));
            btPriorityFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btCustomFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btLowFee.setTypeface(null, Typeface.NORMAL);
            btAutoFee.setTypeface(null, Typeface.BOLD);
            btPriorityFee.setTypeface(null, Typeface.NORMAL);
            btCustomFee.setTypeface(null, Typeface.NORMAL);
            tvFeePrompt.setText(getText(R.string.fee_mid_priority) + " " + getText(R.string.blocks_to_cf));
            break;
    }
    btLowFee.setText((FeeUtil.getInstance().getLowFee().getDefaultPerKB().longValue() / 1000L) + "\n" + getString(R.string.sat_b));
    btPriorityFee.setText((FeeUtil.getInstance().getHighFee().getDefaultPerKB().longValue() / 1000L) + "\n" + getString(R.string.sat_b));
    btAutoFee.setText((FeeUtil.getInstance().getNormalFee().getDefaultPerKB().longValue() / 1000L) + "\n" + getString(R.string.sat_b));
    btLowFee.setOnClickListener(new View.OnClickListener() {

        public void onClick(View v) {
            FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getLowFee());
            PrefsUtil.getInstance(SendActivity.this).setValue(PrefsUtil.CURRENT_FEE_TYPE, FEE_LOW);
            btLowFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.blue));
            btAutoFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btPriorityFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btCustomFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btLowFee.setTypeface(null, Typeface.BOLD);
            btAutoFee.setTypeface(null, Typeface.NORMAL);
            btPriorityFee.setTypeface(null, Typeface.NORMAL);
            btCustomFee.setTypeface(null, Typeface.NORMAL);
            btCustomFee.setText(R.string.custom_fee);
            tvFeePrompt.setText(getText(R.string.fee_low_priority) + " " + getText(R.string.blocks_to_cf));
        }
    });
    btAutoFee.setOnClickListener(new View.OnClickListener() {

        public void onClick(View v) {
            FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getNormalFee());
            PrefsUtil.getInstance(SendActivity.this).setValue(PrefsUtil.CURRENT_FEE_TYPE, FEE_NORMAL);
            btLowFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btAutoFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.blue));
            btPriorityFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btCustomFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btLowFee.setTypeface(null, Typeface.NORMAL);
            btAutoFee.setTypeface(null, Typeface.BOLD);
            btPriorityFee.setTypeface(null, Typeface.NORMAL);
            btCustomFee.setTypeface(null, Typeface.NORMAL);
            btCustomFee.setText(R.string.custom_fee);
            tvFeePrompt.setText(getText(R.string.fee_mid_priority) + " " + getText(R.string.blocks_to_cf));
        }
    });
    btPriorityFee.setOnClickListener(new View.OnClickListener() {

        public void onClick(View v) {
            FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getHighFee());
            PrefsUtil.getInstance(SendActivity.this).setValue(PrefsUtil.CURRENT_FEE_TYPE, FEE_PRIORITY);
            btLowFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btAutoFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btPriorityFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.blue));
            btCustomFee.setBackgroundColor(SendActivity.this.getResources().getColor(R.color.darkgrey));
            btLowFee.setTypeface(null, Typeface.NORMAL);
            btAutoFee.setTypeface(null, Typeface.NORMAL);
            btPriorityFee.setTypeface(null, Typeface.BOLD);
            btCustomFee.setTypeface(null, Typeface.NORMAL);
            btCustomFee.setText(R.string.custom_fee);
            tvFeePrompt.setText(getText(R.string.fee_high_priority) + " " + getText(R.string.blocks_to_cf));
        }
    });
    btCustomFee.setOnClickListener(new View.OnClickListener() {

        public void onClick(View v) {
            doCustomFee();
        }
    });
    btSend = (Button) findViewById(R.id.send);
    btSend.setOnClickListener(new View.OnClickListener() {

        public void onClick(View v) {
            btSend.setClickable(false);
            btSend.setActivated(false);
            double btc_amount = 0.0;
            try {
                btc_amount = NumberFormat.getInstance(Locale.US).parse(edAmountBTC.getText().toString().trim()).doubleValue();
            // Log.i("SendFragment", "amount entered:" + btc_amount);
            } catch (NumberFormatException nfe) {
                btc_amount = 0.0;
            } catch (ParseException pe) {
                btc_amount = 0.0;
            }
            double dAmount = btc_amount;
            long amount = (long) (Math.round(dAmount * 1e8));
            ;
            // Log.i("SendActivity", "amount:" + amount);
            final String address = strDestinationBTCAddress == null ? edAddress.getText().toString() : strDestinationBTCAddress;
            final int accountIdx = selectedAccount;
            final boolean isSegwitChange = (FormatsUtil.getInstance().isValidBech32(address) || Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) || PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.USE_LIKE_TYPED_CHANGE, true) == false;
            final HashMap<String, BigInteger> receivers = new HashMap<String, BigInteger>();
            receivers.put(address, BigInteger.valueOf(amount));
            // store current change index to restore value in case of sending fail
            int change_index = 0;
            if (isSegwitChange) {
                change_index = BIP49Util.getInstance(SendActivity.this).getWallet().getAccount(0).getChange().getAddrIdx();
            } else {
                try {
                    change_index = HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(0).getChange().getAddrIdx();
                // Log.d("SendActivity", "storing change index:" + change_index);
                } catch (IOException ioe) {
                    ;
                } catch (MnemonicException.MnemonicLengthException mle) {
                    ;
                }
            }
            // get all UTXO
            List<UTXO> utxos = null;
            // if possible, get UTXO by input 'type': p2pkh or p2sh-p2wpkh, else get all UTXO
            long neededAmount = 0L;
            if (FormatsUtil.getInstance().isValidBech32(address) || Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) {
                neededAmount += FeeUtil.getInstance().estimatedFeeSegwit(0, UTXOFactory.getInstance().getCountP2SH_P2WPKH(), 4).longValue();
            // Log.d("SendActivity", "segwit:" + neededAmount);
            } else {
                neededAmount += FeeUtil.getInstance().estimatedFeeSegwit(UTXOFactory.getInstance().getCountP2PKH(), 0, 4).longValue();
            // Log.d("SendActivity", "p2pkh:" + neededAmount);
            }
            neededAmount += amount;
            neededAmount += SamouraiWallet.bDust.longValue();
            if ((FormatsUtil.getInstance().isValidBech32(address) || Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) && (UTXOFactory.getInstance().getP2SH_P2WPKH().size() > 0 && UTXOFactory.getInstance().getTotalP2SH_P2WPKH() > neededAmount)) {
                utxos = new ArrayList<UTXO>(UTXOFactory.getInstance().getP2SH_P2WPKH().values());
            // Log.d("SendActivity", "segwit utxos:" + utxos.size());
            } else if ((UTXOFactory.getInstance().getP2PKH().size() > 0) && (UTXOFactory.getInstance().getTotalP2PKH() > neededAmount)) {
                utxos = new ArrayList<UTXO>(UTXOFactory.getInstance().getP2PKH().values());
            // Log.d("SendActivity", "p2pkh utxos:" + utxos.size());
            } else {
                utxos = APIFactory.getInstance(SendActivity.this).getUtxos(true);
            // Log.d("SendActivity", "all filtered utxos:" + utxos.size());
            }
            final List<UTXO> selectedUTXO = new ArrayList<UTXO>();
            long totalValueSelected = 0L;
            long change = 0L;
            BigInteger fee = null;
            // insufficient funds
            if (amount > balance) {
                Toast.makeText(SendActivity.this, R.string.insufficient_funds, Toast.LENGTH_SHORT).show();
            } else // entire balance (can only be simple spend)
            if (amount == balance) {
                // make sure we are using simple spend
                SPEND_TYPE = SPEND_SIMPLE;
                // Log.d("SendActivity", "amount == balance");
                // take all utxos, deduct fee
                selectedUTXO.addAll(utxos);
                for (UTXO u : selectedUTXO) {
                    totalValueSelected += u.getValue();
                }
            // Log.d("SendActivity", "balance:" + balance);
            // Log.d("SendActivity", "total value selected:" + totalValueSelected);
            } else {
                ;
            }
            org.apache.commons.lang3.tuple.Pair<ArrayList<MyTransactionOutPoint>, ArrayList<TransactionOutput>> pair = null;
            if (SPEND_TYPE == SPEND_RICOCHET) {
                boolean samouraiFeeViaBIP47 = false;
                if (BIP47Meta.getInstance().getOutgoingStatus(BIP47Meta.strSamouraiDonationPCode) == BIP47Meta.STATUS_SENT_CFM) {
                    samouraiFeeViaBIP47 = true;
                }
                final JSONObject jObj = RicochetMeta.getInstance(SendActivity.this).script(amount, FeeUtil.getInstance().getSuggestedFee().getDefaultPerKB().longValue(), address, 4, strPCode, samouraiFeeViaBIP47);
                if (jObj != null) {
                    try {
                        long totalAmount = jObj.getLong("total_spend");
                        if (totalAmount > balance) {
                            Toast.makeText(SendActivity.this, R.string.insufficient_funds, Toast.LENGTH_SHORT).show();
                            return;
                        }
                        String msg = getText(R.string.ricochet_spend1) + " " + address + " " + getText(R.string.ricochet_spend2) + " " + Coin.valueOf(totalAmount).toPlainString() + " " + getText(R.string.ricochet_spend3);
                        AlertDialog.Builder dlg = new AlertDialog.Builder(SendActivity.this).setTitle(R.string.app_name).setMessage(msg).setCancelable(false).setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {

                            public void onClick(DialogInterface dialog, int whichButton) {
                                RicochetMeta.getInstance(SendActivity.this).add(jObj);
                                dialog.dismiss();
                                Intent intent = new Intent(SendActivity.this, RicochetActivity.class);
                                startActivityForResult(intent, RICOCHET);
                            }
                        }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {

                            public void onClick(DialogInterface dialog, int whichButton) {
                                dialog.dismiss();
                            }
                        });
                        if (!isFinishing()) {
                            dlg.show();
                        }
                        return;
                    } catch (JSONException je) {
                        return;
                    }
                }
                return;
            } else // if BIP126 try both hetero/alt, if fails change type to SPEND_SIMPLE
            if (SPEND_TYPE == SPEND_BIP126) {
                List<UTXO> _utxos = utxos;
                // Collections.shuffle(_utxos);
                // sort in descending order by value
                Collections.sort(_utxos, new UTXO.UTXOComparator());
                // hetero
                pair = SendFactory.getInstance(SendActivity.this).heterogeneous(_utxos, BigInteger.valueOf(amount), address);
                if (pair == null) {
                    // Collections.sort(_utxos, new UTXO.UTXOComparator());
                    // alt
                    pair = SendFactory.getInstance(SendActivity.this).altHeterogeneous(_utxos, BigInteger.valueOf(amount), address);
                }
                if (pair == null) {
                    // can't do BIP126, revert to SPEND_SIMPLE
                    SPEND_TYPE = SPEND_SIMPLE;
                }
            } else {
                ;
            }
            // simple spend (less than balance)
            if (SPEND_TYPE == SPEND_SIMPLE) {
                List<UTXO> _utxos = utxos;
                // sort in ascending order by value
                Collections.sort(_utxos, new UTXO.UTXOComparator());
                Collections.reverse(_utxos);
                // get smallest 1 UTXO > than spend + fee + dust
                for (UTXO u : _utxos) {
                    Pair<Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(u.getOutpoints());
                    if (u.getValue() >= (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFeeSegwit(outpointTypes.getLeft(), outpointTypes.getRight(), 2).longValue())) {
                        selectedUTXO.add(u);
                        totalValueSelected += u.getValue();
                        // Log.d("SendActivity", "nb inputs:" + u.getOutpoints().size());
                        break;
                    }
                }
                if (selectedUTXO.size() == 0) {
                    // sort in descending order by value
                    Collections.sort(_utxos, new UTXO.UTXOComparator());
                    int selected = 0;
                    int p2pkh = 0;
                    int p2wpkh = 0;
                    // get largest UTXOs > than spend + fee + dust
                    for (UTXO u : _utxos) {
                        selectedUTXO.add(u);
                        totalValueSelected += u.getValue();
                        selected += u.getOutpoints().size();
                        // Log.d("SendActivity", "value selected:" + u.getValue());
                        // Log.d("SendActivity", "total value selected/threshold:" + totalValueSelected + "/" + (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFee(selected, 2).longValue()));
                        Pair<Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(u.getOutpoints());
                        p2pkh += outpointTypes.getLeft();
                        p2wpkh += outpointTypes.getRight();
                        if (totalValueSelected >= (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFeeSegwit(p2pkh, p2wpkh, 2).longValue())) {
                            // Log.d("SendActivity", "nb inputs:" + selected);
                            break;
                        }
                    }
                }
            } else if (pair != null) {
                selectedUTXO.clear();
                receivers.clear();
                long inputAmount = 0L;
                long outputAmount = 0L;
                for (MyTransactionOutPoint outpoint : pair.getLeft()) {
                    UTXO u = new UTXO();
                    List<MyTransactionOutPoint> outs = new ArrayList<MyTransactionOutPoint>();
                    outs.add(outpoint);
                    u.setOutpoints(outs);
                    totalValueSelected += u.getValue();
                    selectedUTXO.add(u);
                    inputAmount += u.getValue();
                }
                for (TransactionOutput output : pair.getRight()) {
                    try {
                        Script script = new Script(output.getScriptBytes());
                        receivers.put(script.getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString(), BigInteger.valueOf(output.getValue().longValue()));
                        outputAmount += output.getValue().longValue();
                    } catch (Exception e) {
                        Toast.makeText(SendActivity.this, R.string.error_bip126_output, Toast.LENGTH_SHORT).show();
                        return;
                    }
                }
                change = outputAmount - amount;
                fee = BigInteger.valueOf(inputAmount - outputAmount);
            } else {
                Toast.makeText(SendActivity.this, R.string.cannot_select_utxo, Toast.LENGTH_SHORT).show();
                return;
            }
            // do spend here
            if (selectedUTXO.size() > 0) {
                // estimate fee for simple spend, already done if BIP126
                if (SPEND_TYPE == SPEND_SIMPLE) {
                    List<MyTransactionOutPoint> outpoints = new ArrayList<MyTransactionOutPoint>();
                    for (UTXO utxo : selectedUTXO) {
                        outpoints.addAll(utxo.getOutpoints());
                    }
                    Pair<Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(outpoints);
                    fee = FeeUtil.getInstance().estimatedFeeSegwit(outpointTypes.getLeft(), outpointTypes.getRight(), 2);
                }
                // Log.d("SendActivity", "spend type:" + SPEND_TYPE);
                // Log.d("SendActivity", "amount:" + amount);
                // Log.d("SendActivity", "total value selected:" + totalValueSelected);
                // Log.d("SendActivity", "fee:" + fee.longValue());
                // Log.d("SendActivity", "nb inputs:" + selectedUTXO.size());
                change = totalValueSelected - (amount + fee.longValue());
                // Log.d("SendActivity", "change:" + change);
                boolean changeIsDust = false;
                if (change < SamouraiWallet.bDust.longValue() && SPEND_TYPE == SPEND_SIMPLE) {
                    change = 0L;
                    fee = fee.add(BigInteger.valueOf(change));
                    amount = totalValueSelected - fee.longValue();
                    // Log.d("SendActivity", "fee:" + fee.longValue());
                    // Log.d("SendActivity", "change:" + change);
                    // Log.d("SendActivity", "amount:" + amount);
                    receivers.put(address, BigInteger.valueOf(amount));
                    changeIsDust = true;
                }
                final long _change = change;
                final BigInteger _fee = fee;
                final int _change_index = change_index;
                String dest = null;
                if (strPCode != null && strPCode.length() > 0) {
                    dest = BIP47Meta.getInstance().getDisplayLabel(strPCode);
                } else {
                    dest = address;
                }
                final String strPrivacyWarning;
                if (SendAddressUtil.getInstance().get(address) == 1) {
                    strPrivacyWarning = getString(R.string.send_privacy_warning) + "\n\n";
                } else {
                    strPrivacyWarning = "";
                }
                String strChangeIsDust = null;
                if (changeIsDust) {
                    strChangeIsDust = getString(R.string.change_is_dust) + "\n\n";
                } else {
                    strChangeIsDust = "";
                }
                String message = strChangeIsDust + strPrivacyWarning + "Send " + Coin.valueOf(amount).toPlainString() + " to " + dest + " (fee:" + Coin.valueOf(_fee.longValue()).toPlainString() + ")?\n";
                final long _amount = amount;
                AlertDialog.Builder builder = new AlertDialog.Builder(SendActivity.this);
                builder.setTitle(R.string.app_name);
                builder.setMessage(message);
                final CheckBox cbShowAgain;
                if (strPrivacyWarning.length() > 0) {
                    cbShowAgain = new CheckBox(SendActivity.this);
                    cbShowAgain.setText(R.string.do_not_repeat_sent_to);
                    cbShowAgain.setChecked(false);
                    builder.setView(cbShowAgain);
                } else {
                    cbShowAgain = null;
                }
                builder.setCancelable(false);
                builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {

                    public void onClick(final DialogInterface dialog, int whichButton) {
                        final ProgressDialog progress = new ProgressDialog(SendActivity.this);
                        progress.setCancelable(false);
                        progress.setTitle(R.string.app_name);
                        progress.setMessage(getString(R.string.please_wait_sending));
                        progress.show();
                        final List<MyTransactionOutPoint> outPoints = new ArrayList<MyTransactionOutPoint>();
                        for (UTXO u : selectedUTXO) {
                            outPoints.addAll(u.getOutpoints());
                        }
                        // add change
                        if (_change > 0L) {
                            if (SPEND_TYPE == SPEND_SIMPLE) {
                                if (isSegwitChange) {
                                    String change_address = BIP49Util.getInstance(SendActivity.this).getAddressAt(AddressFactory.CHANGE_CHAIN, BIP49Util.getInstance(SendActivity.this).getWallet().getAccount(0).getChange().getAddrIdx()).getAddressAsString();
                                    receivers.put(change_address, BigInteger.valueOf(_change));
                                } else {
                                    try {
                                        String change_address = HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(0).getChange().getAddressAt(HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(0).getChange().getAddrIdx()).getAddressString();
                                        receivers.put(change_address, BigInteger.valueOf(_change));
                                    } catch (IOException ioe) {
                                        Toast.makeText(SendActivity.this, R.string.error_change_output, Toast.LENGTH_SHORT).show();
                                        return;
                                    } catch (MnemonicException.MnemonicLengthException mle) {
                                        Toast.makeText(SendActivity.this, R.string.error_change_output, Toast.LENGTH_SHORT).show();
                                        return;
                                    }
                                }
                            } else if (SPEND_TYPE == SPEND_BIP126) {
                            // do nothing, change addresses included
                            } else {
                                ;
                            }
                        }
                        // make tx
                        Transaction tx = SendFactory.getInstance(SendActivity.this).makeTransaction(0, outPoints, receivers);
                        final RBFSpend rbf;
                        if (PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.RBF_OPT_IN, false) == true) {
                            rbf = new RBFSpend();
                            for (TransactionInput input : tx.getInputs()) {
                                boolean _isBIP49 = false;
                                String _addr = null;
                                Address _address = input.getConnectedOutput().getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams());
                                if (_address != null) {
                                    _addr = _address.toString();
                                }
                                if (_addr == null) {
                                    _addr = input.getConnectedOutput().getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
                                    _isBIP49 = true;
                                }
                                String path = APIFactory.getInstance(SendActivity.this).getUnspentPaths().get(_addr);
                                if (path != null) {
                                    if (_isBIP49) {
                                        rbf.addKey(input.getOutpoint().toString(), path + "/49");
                                    } else {
                                        rbf.addKey(input.getOutpoint().toString(), path);
                                    }
                                } else {
                                    String pcode = BIP47Meta.getInstance().getPCode4Addr(_addr);
                                    int idx = BIP47Meta.getInstance().getIdx4Addr(_addr);
                                    rbf.addKey(input.getOutpoint().toString(), pcode + "/" + idx);
                                }
                            }
                        } else {
                            rbf = null;
                        }
                        if (tx != null) {
                            tx = SendFactory.getInstance(SendActivity.this).signTransaction(tx);
                            final Transaction _tx = tx;
                            final String hexTx = new String(Hex.encode(tx.bitcoinSerialize()));
                            // Log.d("SendActivity", hexTx);
                            final String strTxHash = tx.getHashAsString();
                            if (PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.BROADCAST_TX, true) == false) {
                                if (progress != null && progress.isShowing()) {
                                    progress.dismiss();
                                }
                                doShowTx(hexTx, strTxHash);
                                return;
                            }
                            new Thread(new Runnable() {

                                @Override
                                public void run() {
                                    Looper.prepare();
                                    boolean isOK = false;
                                    String response = null;
                                    try {
                                        if (PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.USE_TRUSTED_NODE, false) == true) {
                                            if (TrustedNodeUtil.getInstance().isSet()) {
                                                response = PushTx.getInstance(SendActivity.this).trustedNode(hexTx);
                                                JSONObject jsonObject = new org.json.JSONObject(response);
                                                if (jsonObject.has("result")) {
                                                    if (jsonObject.getString("result").matches("^[A-Za-z0-9]{64}$")) {
                                                        isOK = true;
                                                    } else {
                                                        Toast.makeText(SendActivity.this, R.string.trusted_node_tx_error, Toast.LENGTH_SHORT).show();
                                                    }
                                                }
                                            } else {
                                                Toast.makeText(SendActivity.this, R.string.trusted_node_not_valid, Toast.LENGTH_SHORT).show();
                                            }
                                        } else {
                                            response = PushTx.getInstance(SendActivity.this).samourai(hexTx);
                                            if (response != null) {
                                                JSONObject jsonObject = new org.json.JSONObject(response);
                                                if (jsonObject.has("status")) {
                                                    if (jsonObject.getString("status").equals("ok")) {
                                                        isOK = true;
                                                    }
                                                }
                                            } else {
                                                Toast.makeText(SendActivity.this, R.string.pushtx_returns_null, Toast.LENGTH_SHORT).show();
                                            }
                                        }
                                        if (isOK) {
                                            if (PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.USE_TRUSTED_NODE, false) == false) {
                                                Toast.makeText(SendActivity.this, R.string.tx_sent, Toast.LENGTH_SHORT).show();
                                            } else {
                                                Toast.makeText(SendActivity.this, R.string.trusted_node_tx_sent, Toast.LENGTH_SHORT).show();
                                            }
                                            if (_change > 0L && SPEND_TYPE == SPEND_SIMPLE) {
                                                if (isSegwitChange) {
                                                    BIP49Util.getInstance(SendActivity.this).getWallet().getAccount(0).getChange().incAddrIdx();
                                                } else {
                                                    try {
                                                        HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(0).getChange().incAddrIdx();
                                                    } catch (IOException ioe) {
                                                        ;
                                                    } catch (MnemonicException.MnemonicLengthException mle) {
                                                        ;
                                                    }
                                                }
                                            }
                                            if (PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.RBF_OPT_IN, false) == true) {
                                                for (TransactionOutput out : _tx.getOutputs()) {
                                                    try {
                                                        if (!isSegwitChange && !address.equals(out.getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString())) {
                                                            rbf.addChangeAddr(out.getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString());
                                                        } else if (isSegwitChange && !address.equals(out.getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString())) {
                                                            rbf.addChangeAddr(out.getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString());
                                                        } else {
                                                            ;
                                                        }
                                                    } catch (NullPointerException npe) {
                                                        // test for bech32, skip for now as it's not a change address
                                                        ;
                                                    }
                                                }
                                                rbf.setHash(strTxHash);
                                                rbf.setSerializedTx(hexTx);
                                                RBFUtil.getInstance().add(rbf);
                                            }
                                            // increment counter if BIP47 spend
                                            if (strPCode != null && strPCode.length() > 0) {
                                                BIP47Meta.getInstance().getPCode4AddrLookup().put(address, strPCode);
                                                BIP47Meta.getInstance().inc(strPCode);
                                                SimpleDateFormat sd = new SimpleDateFormat("dd MMM");
                                                String strTS = sd.format(currentTimeMillis());
                                                String event = strTS + " " + SendActivity.this.getString(R.string.sent) + " " + MonetaryUtil.getInstance().getBTCFormat().format((double) _amount / 1e8) + " BTC";
                                                BIP47Meta.getInstance().setLatestEvent(strPCode, event);
                                                strPCode = null;
                                            }
                                            if (strPrivacyWarning.length() > 0 && cbShowAgain != null) {
                                                SendAddressUtil.getInstance().add(address, cbShowAgain.isChecked() ? false : true);
                                            } else if (SendAddressUtil.getInstance().get(address) == 0) {
                                                SendAddressUtil.getInstance().add(address, false);
                                            } else {
                                                SendAddressUtil.getInstance().add(address, true);
                                            }
                                            Intent intent = new Intent("com.samourai.wallet.BalanceFragment.REFRESH");
                                            intent.putExtra("notifTx", false);
                                            intent.putExtra("fetch", true);
                                            LocalBroadcastManager.getInstance(SendActivity.this).sendBroadcast(intent);
                                            View view = SendActivity.this.getCurrentFocus();
                                            if (view != null) {
                                                InputMethodManager imm = (InputMethodManager) SendActivity.this.getSystemService(Context.INPUT_METHOD_SERVICE);
                                                imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
                                            }
                                            if (bViaMenu) {
                                                SendActivity.this.finish();
                                            } else {
                                                Intent _intent = new Intent(SendActivity.this, BalanceActivity.class);
                                                _intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                                                startActivity(_intent);
                                            }
                                        } else {
                                            Toast.makeText(SendActivity.this, R.string.tx_failed, Toast.LENGTH_SHORT).show();
                                            // reset change index upon tx fail
                                            if (isSegwitChange) {
                                                BIP49Util.getInstance(SendActivity.this).getWallet().getAccount(0).getChange().setAddrIdx(_change_index);
                                            } else {
                                                HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(0).getChange().setAddrIdx(_change_index);
                                            }
                                        }
                                    } catch (JSONException je) {
                                        Toast.makeText(SendActivity.this, "pushTx:" + je.getMessage(), Toast.LENGTH_SHORT).show();
                                    } catch (MnemonicException.MnemonicLengthException mle) {
                                        Toast.makeText(SendActivity.this, "pushTx:" + mle.getMessage(), Toast.LENGTH_SHORT).show();
                                    } catch (DecoderException de) {
                                        Toast.makeText(SendActivity.this, "pushTx:" + de.getMessage(), Toast.LENGTH_SHORT).show();
                                    } catch (IOException ioe) {
                                        Toast.makeText(SendActivity.this, "pushTx:" + ioe.getMessage(), Toast.LENGTH_SHORT).show();
                                    } finally {
                                        SendActivity.this.runOnUiThread(new Runnable() {

                                            @Override
                                            public void run() {
                                                btSend.setActivated(true);
                                                btSend.setClickable(true);
                                                progress.dismiss();
                                                dialog.dismiss();
                                            }
                                        });
                                    }
                                    Looper.loop();
                                }
                            }).start();
                        } else {
                            // Log.d("SendActivity", "tx error");
                            Toast.makeText(SendActivity.this, "tx error", Toast.LENGTH_SHORT).show();
                        }
                    }
                });
                builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {

                    public void onClick(final DialogInterface dialog, int whichButton) {
                        try {
                            // reset change index upon 'NO'
                            if (isSegwitChange) {
                                BIP49Util.getInstance(SendActivity.this).getWallet().getAccount(0).getChange().setAddrIdx(_change_index);
                            } else {
                                HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(0).getChange().setAddrIdx(_change_index);
                            }
                        } catch (Exception e) {
                            // Log.d("SendActivity", e.getMessage());
                            Toast.makeText(SendActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
                        } finally {
                            SendActivity.this.runOnUiThread(new Runnable() {

                                @Override
                                public void run() {
                                    btSend.setActivated(true);
                                    btSend.setClickable(true);
                                    dialog.dismiss();
                                }
                            });
                        }
                    }
                });
                AlertDialog alert = builder.create();
                alert.show();
            }
        }
    });
    Bundle extras = getIntent().getExtras();
    if (extras != null) {
        bViaMenu = extras.getBoolean("via_menu", false);
        String strUri = extras.getString("uri");
        strPCode = extras.getString("pcode");
        if (strUri != null && strUri.length() > 0) {
            processScan(strUri);
        }
        if (strPCode != null && strPCode.length() > 0) {
            processPCode(strPCode, null);
        }
    }
    validateSpend();
}
Also used : AlertDialog(android.app.AlertDialog) PaymentAddress(com.samourai.wallet.bip47.rpc.PaymentAddress) Address(org.bitcoinj.core.Address) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) RicochetActivity(com.samourai.wallet.ricochet.RicochetActivity) ProgressDialog(android.app.ProgressDialog) ArrayList(java.util.ArrayList) List(java.util.List) Script(org.bitcoinj.script.Script) SuggestedFee(com.samourai.wallet.send.SuggestedFee) JSONObject(org.json.JSONObject) Transaction(org.bitcoinj.core.Transaction) CheckBox(android.widget.CheckBox) BigInteger(java.math.BigInteger) TransactionOutput(org.bitcoinj.core.TransactionOutput) DialogInterface(android.content.DialogInterface) DecimalFormat(java.text.DecimalFormat) InputMethodManager(android.view.inputmethod.InputMethodManager) TransactionInput(org.bitcoinj.core.TransactionInput) MnemonicException(org.bitcoinj.crypto.MnemonicException) TextWatcher(android.text.TextWatcher) Editable(android.text.Editable) RBFSpend(com.samourai.wallet.send.RBFSpend) Pair(org.apache.commons.lang3.tuple.Pair) DecimalFormatSymbols(java.text.DecimalFormatSymbols) Bundle(android.os.Bundle) JSONException(org.json.JSONException) Intent(android.content.Intent) IOException(java.io.IOException) ImageView(android.widget.ImageView) View(android.view.View) TextView(android.widget.TextView) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) Point(android.graphics.Point) JSONException(org.json.JSONException) IOException(java.io.IOException) WriterException(com.google.zxing.WriterException) ParseException(java.text.ParseException) DecoderException(org.bouncycastle.util.encoders.DecoderException) FileNotFoundException(java.io.FileNotFoundException) MnemonicException(org.bitcoinj.crypto.MnemonicException) MotionEvent(android.view.MotionEvent) UTXO(com.samourai.wallet.send.UTXO) BlockedUTXO(com.samourai.wallet.send.BlockedUTXO) DecoderException(org.bouncycastle.util.encoders.DecoderException) ParseException(java.text.ParseException) SimpleDateFormat(java.text.SimpleDateFormat) ArrayAdapter(android.widget.ArrayAdapter) CompoundButton(android.widget.CompoundButton) NumberFormat(java.text.NumberFormat)

Example 4 with RBFSpend

use of com.samourai.wallet.send.RBFSpend in project samourai-wallet-android by Samourai-Wallet.

the class RBFTask method doInBackground.

@Override
protected String doInBackground(final String... params) {
    Looper.prepare();
    Log.d("RBF", "hash:" + params[0]);
    rbf = RBFUtil.getInstance().get(params[0]);
    Log.d("RBF", "rbf:" + rbf.toJSON().toString());
    final Transaction tx = new Transaction(SamouraiWallet.getInstance().getCurrentNetworkParams(), Hex.decode(rbf.getSerializedTx()));
    Log.d("RBF", "tx serialized:" + rbf.getSerializedTx());
    Log.d("RBF", "tx inputs:" + tx.getInputs().size());
    Log.d("RBF", "tx outputs:" + tx.getOutputs().size());
    JSONObject txObj = APIFactory.getInstance(activity).getTxInfo(params[0]);
    if (tx != null && txObj.has("inputs") && txObj.has("outputs")) {
        try {
            JSONArray inputs = txObj.getJSONArray("inputs");
            JSONArray outputs = txObj.getJSONArray("outputs");
            int p2pkh = 0;
            int p2sh_p2wpkh = 0;
            int p2wpkh = 0;
            for (int i = 0; i < inputs.length(); i++) {
                if (inputs.getJSONObject(i).has("outpoint") && inputs.getJSONObject(i).getJSONObject("outpoint").has("scriptpubkey")) {
                    String scriptpubkey = inputs.getJSONObject(i).getJSONObject("outpoint").getString("scriptpubkey");
                    Script script = new Script(Hex.decode(scriptpubkey));
                    String address = null;
                    if (Bech32Util.getInstance().isBech32Script(scriptpubkey)) {
                        try {
                            address = Bech32Util.getInstance().getAddressFromScript(scriptpubkey);
                        } catch (Exception e) {
                            ;
                        }
                    } else {
                        address = script.getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
                    }
                    if (FormatsUtil.getInstance().isValidBech32(address)) {
                        p2wpkh++;
                    } else if (Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) {
                        p2sh_p2wpkh++;
                    } else {
                        p2pkh++;
                    }
                }
            }
            SuggestedFee suggestedFee = FeeUtil.getInstance().getSuggestedFee();
            FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getHighFee());
            BigInteger estimatedFee = FeeUtil.getInstance().estimatedFeeSegwit(p2pkh, p2sh_p2wpkh, p2wpkh, outputs.length());
            long total_inputs = 0L;
            long total_outputs = 0L;
            long fee = 0L;
            long total_change = 0L;
            List<String> selfAddresses = new ArrayList<String>();
            for (int i = 0; i < inputs.length(); i++) {
                JSONObject obj = inputs.getJSONObject(i);
                if (obj.has("outpoint")) {
                    JSONObject objPrev = obj.getJSONObject("outpoint");
                    if (objPrev.has("value")) {
                        total_inputs += objPrev.getLong("value");
                        String key = objPrev.getString("txid") + ":" + objPrev.getLong("vout");
                        input_values.put(key, objPrev.getLong("value"));
                    }
                }
            }
            for (int i = 0; i < outputs.length(); i++) {
                JSONObject obj = outputs.getJSONObject(i);
                if (obj.has("value")) {
                    total_outputs += obj.getLong("value");
                    String _addr = null;
                    if (obj.has("address")) {
                        _addr = obj.getString("address");
                    }
                    selfAddresses.add(_addr);
                    if (_addr != null && rbf.getChangeAddrs().contains(_addr.toString())) {
                        total_change += obj.getLong("value");
                    }
                }
            }
            boolean feeWarning = false;
            fee = total_inputs - total_outputs;
            if (fee > estimatedFee.longValue()) {
                feeWarning = true;
            }
            long remainingFee = (estimatedFee.longValue() > fee) ? estimatedFee.longValue() - fee : 0L;
            Log.d("RBF", "total inputs:" + total_inputs);
            Log.d("RBF", "total outputs:" + total_outputs);
            Log.d("RBF", "total change:" + total_change);
            Log.d("RBF", "fee:" + fee);
            Log.d("RBF", "estimated fee:" + estimatedFee.longValue());
            Log.d("RBF", "fee warning:" + feeWarning);
            Log.d("RBF", "remaining fee:" + remainingFee);
            List<TransactionOutput> txOutputs = new ArrayList<TransactionOutput>();
            txOutputs.addAll(tx.getOutputs());
            long remainder = remainingFee;
            if (total_change > remainder) {
                for (TransactionOutput output : txOutputs) {
                    Script script = output.getScriptPubKey();
                    String scriptPubKey = Hex.toHexString(script.getProgram());
                    Address _p2sh = output.getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams());
                    Address _p2pkh = output.getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams());
                    try {
                        if ((Bech32Util.getInstance().isBech32Script(scriptPubKey) && rbf.getChangeAddrs().contains(Bech32Util.getInstance().getAddressFromScript(scriptPubKey))) || (_p2sh != null && rbf.getChangeAddrs().contains(_p2sh.toString())) || (_p2pkh != null && rbf.getChangeAddrs().contains(_p2pkh.toString()))) {
                            if (output.getValue().longValue() >= (remainder + SamouraiWallet.bDust.longValue())) {
                                output.setValue(Coin.valueOf(output.getValue().longValue() - remainder));
                                remainder = 0L;
                                break;
                            } else {
                                remainder -= output.getValue().longValue();
                                // output will be discarded later
                                output.setValue(Coin.valueOf(0L));
                            }
                        }
                    } catch (Exception e) {
                        ;
                    }
                }
            }
            // 
            // original inputs are not modified
            // 
            List<MyTransactionInput> _inputs = new ArrayList<MyTransactionInput>();
            List<TransactionInput> txInputs = tx.getInputs();
            for (TransactionInput input : txInputs) {
                MyTransactionInput _input = new MyTransactionInput(SamouraiWallet.getInstance().getCurrentNetworkParams(), null, new byte[0], input.getOutpoint(), input.getOutpoint().getHash().toString(), (int) input.getOutpoint().getIndex());
                _input.setSequenceNumber(SamouraiWallet.RBF_SEQUENCE_VAL.longValue());
                _inputs.add(_input);
                Log.d("RBF", "add outpoint:" + _input.getOutpoint().toString());
            }
            Triple<Integer, Integer, Integer> outpointTypes = null;
            if (remainder > 0L) {
                List<UTXO> selectedUTXO = new ArrayList<UTXO>();
                long selectedAmount = 0L;
                int selected = 0;
                long _remainingFee = remainder;
                Collections.sort(utxos, new UTXO.UTXOComparator());
                for (UTXO _utxo : utxos) {
                    Log.d("RBF", "utxo value:" + _utxo.getValue());
                    // 
                    // do not select utxo that are change outputs in current rbf tx
                    // 
                    boolean isChange = false;
                    boolean isSelf = false;
                    for (MyTransactionOutPoint outpoint : _utxo.getOutpoints()) {
                        if (rbf.containsChangeAddr(outpoint.getAddress())) {
                            Log.d("RBF", "is change:" + outpoint.getAddress());
                            Log.d("RBF", "is change:" + outpoint.getValue().longValue());
                            isChange = true;
                            break;
                        }
                        if (selfAddresses.contains(outpoint.getAddress())) {
                            Log.d("RBF", "is self:" + outpoint.getAddress());
                            Log.d("RBF", "is self:" + outpoint.getValue().longValue());
                            isSelf = true;
                            break;
                        }
                    }
                    if (isChange || isSelf) {
                        continue;
                    }
                    selectedUTXO.add(_utxo);
                    selected += _utxo.getOutpoints().size();
                    Log.d("RBF", "selected utxo:" + selected);
                    selectedAmount += _utxo.getValue();
                    Log.d("RBF", "selected utxo value:" + _utxo.getValue());
                    outpointTypes = FeeUtil.getInstance().getOutpointCount(new Vector(_utxo.getOutpoints()));
                    p2pkh += outpointTypes.getLeft();
                    p2sh_p2wpkh += outpointTypes.getMiddle();
                    p2wpkh += outpointTypes.getRight();
                    _remainingFee = FeeUtil.getInstance().estimatedFeeSegwit(p2pkh, p2sh_p2wpkh, p2wpkh, outputs.length() == 1 ? 2 : outputs.length()).longValue();
                    Log.d("RBF", "_remaining fee:" + _remainingFee);
                    if (selectedAmount >= (_remainingFee + SamouraiWallet.bDust.longValue())) {
                        break;
                    }
                }
                long extraChange = 0L;
                if (selectedAmount < (_remainingFee + SamouraiWallet.bDust.longValue())) {
                    handler.post(new Runnable() {

                        public void run() {
                            Toast.makeText(activity, R.string.insufficient_funds, Toast.LENGTH_SHORT).show();
                        }
                    });
                    return "KO";
                } else {
                    extraChange = selectedAmount - _remainingFee;
                    Log.d("RBF", "extra change:" + extraChange);
                }
                boolean addedChangeOutput = false;
                // parent tx didn't have change output
                if (outputs.length() == 1 && extraChange > 0L) {
                    try {
                        boolean isSegwitChange = (FormatsUtil.getInstance().isValidBech32(outputs.getJSONObject(0).getString("address")) || Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), outputs.getJSONObject(0).getString("address")).isP2SHAddress()) || PrefsUtil.getInstance(activity).getValue(PrefsUtil.USE_LIKE_TYPED_CHANGE, true) == false;
                        String change_address = null;
                        if (isSegwitChange) {
                            int changeIdx = BIP49Util.getInstance(activity).getWallet().getAccount(0).getChange().getAddrIdx();
                            change_address = BIP49Util.getInstance(activity).getAddressAt(AddressFactory.CHANGE_CHAIN, changeIdx).getAddressAsString();
                        } else {
                            int changeIdx = HD_WalletFactory.getInstance(activity).get().getAccount(0).getChange().getAddrIdx();
                            change_address = HD_WalletFactory.getInstance(activity).get().getAccount(0).getChange().getAddressAt(changeIdx).getAddressString();
                        }
                        Script toOutputScript = ScriptBuilder.createOutputScript(Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), change_address));
                        TransactionOutput output = new TransactionOutput(SamouraiWallet.getInstance().getCurrentNetworkParams(), null, Coin.valueOf(extraChange), toOutputScript.getProgram());
                        txOutputs.add(output);
                        addedChangeOutput = true;
                    } catch (MnemonicException.MnemonicLengthException | IOException e) {
                        handler.post(new Runnable() {

                            public void run() {
                                Toast.makeText(activity, e.getMessage(), Toast.LENGTH_SHORT).show();
                                Toast.makeText(activity, R.string.cannot_create_change_output, Toast.LENGTH_SHORT).show();
                            }
                        });
                        return "KO";
                    }
                } else // parent tx had change output
                {
                    for (TransactionOutput output : txOutputs) {
                        Script script = output.getScriptPubKey();
                        String scriptPubKey = Hex.toHexString(script.getProgram());
                        String _addr = null;
                        if (Bech32Util.getInstance().isBech32Script(scriptPubKey)) {
                            try {
                                _addr = Bech32Util.getInstance().getAddressFromScript(scriptPubKey);
                            } catch (Exception e) {
                                ;
                            }
                        }
                        if (_addr == null) {
                            Address _address = output.getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams());
                            if (_address == null) {
                                _address = output.getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams());
                            }
                            _addr = _address.toString();
                        }
                        Log.d("RBF", "checking for change:" + _addr);
                        if (rbf.containsChangeAddr(_addr)) {
                            Log.d("RBF", "before extra:" + output.getValue().longValue());
                            output.setValue(Coin.valueOf(extraChange + output.getValue().longValue()));
                            Log.d("RBF", "after extra:" + output.getValue().longValue());
                            addedChangeOutput = true;
                            break;
                        }
                    }
                }
                // sanity check
                if (extraChange > 0L && !addedChangeOutput) {
                    handler.post(new Runnable() {

                        public void run() {
                            Toast.makeText(activity, R.string.cannot_create_change_output, Toast.LENGTH_SHORT).show();
                        }
                    });
                    return "KO";
                }
                // 
                // update keyBag w/ any new paths
                // 
                final HashMap<String, String> keyBag = rbf.getKeyBag();
                for (UTXO _utxo : selectedUTXO) {
                    for (MyTransactionOutPoint outpoint : _utxo.getOutpoints()) {
                        MyTransactionInput _input = new MyTransactionInput(SamouraiWallet.getInstance().getCurrentNetworkParams(), null, new byte[0], outpoint, outpoint.getTxHash().toString(), outpoint.getTxOutputN());
                        _input.setSequenceNumber(SamouraiWallet.RBF_SEQUENCE_VAL.longValue());
                        _inputs.add(_input);
                        Log.d("RBF", "add selected outpoint:" + _input.getOutpoint().toString());
                        String path = APIFactory.getInstance(activity).getUnspentPaths().get(outpoint.getAddress());
                        if (path != null) {
                            if (FormatsUtil.getInstance().isValidBech32(outpoint.getAddress())) {
                                rbf.addKey(outpoint.toString(), path + "/84");
                            } else if (Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), outpoint.getAddress()) != null && Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), outpoint.getAddress()).isP2SHAddress()) {
                                rbf.addKey(outpoint.toString(), path + "/49");
                            } else {
                                rbf.addKey(outpoint.toString(), path);
                            }
                            Log.d("RBF", "outpoint address:" + outpoint.getAddress());
                        } else {
                            String pcode = BIP47Meta.getInstance().getPCode4Addr(outpoint.getAddress());
                            int idx = BIP47Meta.getInstance().getIdx4Addr(outpoint.getAddress());
                            rbf.addKey(outpoint.toString(), pcode + "/" + idx);
                        }
                    }
                }
                rbf.setKeyBag(keyBag);
            }
            // 
            // BIP69 sort of outputs/inputs
            // 
            final Transaction _tx = new Transaction(SamouraiWallet.getInstance().getCurrentNetworkParams());
            List<TransactionOutput> _txOutputs = new ArrayList<TransactionOutput>();
            _txOutputs.addAll(txOutputs);
            Collections.sort(_txOutputs, new BIP69OutputComparator());
            for (TransactionOutput to : _txOutputs) {
                // zero value outputs discarded here
                if (to.getValue().longValue() > 0L) {
                    _tx.addOutput(to);
                }
            }
            List<MyTransactionInput> __inputs = new ArrayList<MyTransactionInput>();
            __inputs.addAll(_inputs);
            Collections.sort(__inputs, new SendFactory.BIP69InputComparator());
            for (TransactionInput input : __inputs) {
                _tx.addInput(input);
            }
            FeeUtil.getInstance().setSuggestedFee(suggestedFee);
            String message = "";
            if (feeWarning) {
                message += activity.getString(R.string.fee_bump_not_necessary);
                message += "\n\n";
            }
            message += activity.getString(R.string.bump_fee) + " " + Coin.valueOf(remainingFee).toPlainString() + " BTC";
            AlertDialog.Builder dlg = new AlertDialog.Builder(activity).setTitle(R.string.app_name).setMessage(message).setCancelable(false).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {

                public void onClick(DialogInterface dialog, int whichButton) {
                    Transaction __tx = signTx(_tx);
                    final String hexTx = new String(Hex.encode(__tx.bitcoinSerialize()));
                    Log.d("RBF", "hex tx:" + hexTx);
                    final String strTxHash = __tx.getHashAsString();
                    Log.d("RBF", "tx hash:" + strTxHash);
                    if (__tx != null) {
                        boolean isOK = false;
                        try {
                            isOK = PushTx.getInstance(activity).pushTx(hexTx);
                            if (isOK) {
                                handler.post(new Runnable() {

                                    public void run() {
                                        Toast.makeText(activity, R.string.rbf_spent, Toast.LENGTH_SHORT).show();
                                        // includes updated 'keyBag'
                                        RBFSpend _rbf = rbf;
                                        _rbf.setSerializedTx(hexTx);
                                        _rbf.setHash(strTxHash);
                                        _rbf.setPrevHash(params[0]);
                                        RBFUtil.getInstance().add(_rbf);
                                        Intent _intent = new Intent(activity, MainActivity2.class);
                                        _intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                                        activity.startActivity(_intent);
                                    }
                                });
                            } else {
                                handler.post(new Runnable() {

                                    public void run() {
                                        Toast.makeText(activity, R.string.tx_failed, Toast.LENGTH_SHORT).show();
                                    }
                                });
                            }
                        } catch (final DecoderException de) {
                            handler.post(new Runnable() {

                                public void run() {
                                    Toast.makeText(activity, "pushTx:" + de.getMessage(), Toast.LENGTH_SHORT).show();
                                }
                            });
                        } finally {
                            ;
                        }
                    }
                }
            }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {

                public void onClick(DialogInterface dialog, int whichButton) {
                    dialog.dismiss();
                }
            });
            if (!activity.isFinishing()) {
                dlg.show();
            }
        } catch (final JSONException je) {
            handler.post(new Runnable() {

                public void run() {
                    Toast.makeText(activity, "rbf:" + je.getMessage(), Toast.LENGTH_SHORT).show();
                }
            });
        }
    } else {
        Toast.makeText(activity, R.string.cpfp_cannot_retrieve_tx, Toast.LENGTH_SHORT).show();
    }
    Looper.loop();
    return "OK";
}
Also used : AlertDialog(android.app.AlertDialog) TransactionOutput(org.bitcoinj.core.TransactionOutput) Address(org.bitcoinj.core.Address) HD_Address(com.samourai.wallet.hd.HD_Address) PaymentAddress(com.samourai.wallet.bip47.rpc.PaymentAddress) SegwitAddress(com.samourai.wallet.segwit.SegwitAddress) DialogInterface(android.content.DialogInterface) ArrayList(java.util.ArrayList) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) MyTransactionInput(com.samourai.wallet.send.MyTransactionInput) TransactionInput(org.bitcoinj.core.TransactionInput) MainActivity2(com.samourai.wallet.MainActivity2) BIP69OutputComparator(com.samourai.wallet.bip69.BIP69OutputComparator) RBFSpend(com.samourai.wallet.send.RBFSpend) Vector(java.util.Vector) Script(org.bitcoinj.script.Script) SendFactory(com.samourai.wallet.send.SendFactory) SuggestedFee(com.samourai.wallet.send.SuggestedFee) JSONArray(org.json.JSONArray) JSONException(org.json.JSONException) Intent(android.content.Intent) IOException(java.io.IOException) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) JSONException(org.json.JSONException) DecoderException(org.bouncycastle.util.encoders.DecoderException) MnemonicException(org.bitcoinj.crypto.MnemonicException) IOException(java.io.IOException) BigInteger(java.math.BigInteger) UTXO(com.samourai.wallet.send.UTXO) DecoderException(org.bouncycastle.util.encoders.DecoderException) Transaction(org.bitcoinj.core.Transaction) JSONObject(org.json.JSONObject) MyTransactionInput(com.samourai.wallet.send.MyTransactionInput) BigInteger(java.math.BigInteger)

Aggregations

RBFSpend (com.samourai.wallet.send.RBFSpend)4 IOException (java.io.IOException)4 Address (org.bitcoinj.core.Address)4 Transaction (org.bitcoinj.core.Transaction)4 TransactionInput (org.bitcoinj.core.TransactionInput)4 MnemonicException (org.bitcoinj.crypto.MnemonicException)4 AlertDialog (android.app.AlertDialog)3 DialogInterface (android.content.DialogInterface)3 Intent (android.content.Intent)3 Point (android.graphics.Point)3 WriterException (com.google.zxing.WriterException)3 PaymentAddress (com.samourai.wallet.bip47.rpc.PaymentAddress)3 MyTransactionOutPoint (com.samourai.wallet.send.MyTransactionOutPoint)3 UTXO (com.samourai.wallet.send.UTXO)3 BigInteger (java.math.BigInteger)3 ArrayList (java.util.ArrayList)3 JSONException (org.json.JSONException)3 JSONObject (org.json.JSONObject)3 SegwitAddress (com.samourai.wallet.segwit.SegwitAddress)2 SuggestedFee (com.samourai.wallet.send.SuggestedFee)2