Search in sources :

Example 1 with MainActivity2

use of com.samourai.wallet.MainActivity2 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)

Example 2 with MainActivity2

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

the class CPFPTask method doInBackground.

@Override
protected String doInBackground(String... params) {
    Looper.prepare();
    Log.d("activity", "hash:" + params[0]);
    JSONObject txObj = APIFactory.getInstance(activity).getTxInfo(params[0]);
    if (txObj.has("inputs") && txObj.has("outputs")) {
        final SuggestedFee suggestedFee = FeeUtil.getInstance().getSuggestedFee();
        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++;
                    }
                }
            }
            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;
            UTXO utxo = null;
            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");
                    }
                }
            }
            for (int i = 0; i < outputs.length(); i++) {
                JSONObject obj = outputs.getJSONObject(i);
                if (obj.has("value")) {
                    total_outputs += obj.getLong("value");
                    String addr = obj.getString("address");
                    Log.d("activity", "checking address:" + addr);
                    if (utxo == null) {
                        utxo = getUTXO(addr);
                    } else {
                        break;
                    }
                }
            }
            boolean feeWarning = false;
            fee = total_inputs - total_outputs;
            if (fee > estimatedFee.longValue()) {
                feeWarning = true;
            }
            Log.d("activity", "total inputs:" + total_inputs);
            Log.d("activity", "total outputs:" + total_outputs);
            Log.d("activity", "fee:" + fee);
            Log.d("activity", "estimated fee:" + estimatedFee.longValue());
            Log.d("activity", "fee warning:" + feeWarning);
            if (utxo != null) {
                Log.d("activity", "utxo found");
                List<UTXO> selectedUTXO = new ArrayList<UTXO>();
                selectedUTXO.add(utxo);
                int selected = utxo.getOutpoints().size();
                long remainingFee = (estimatedFee.longValue() > fee) ? estimatedFee.longValue() - fee : 0L;
                Log.d("activity", "remaining fee:" + remainingFee);
                int receiveIdx = AddressFactory.getInstance(activity).getHighestTxReceiveIdx(0);
                Log.d("activity", "receive index:" + receiveIdx);
                final String addr;
                if (PrefsUtil.getInstance(activity).getValue(PrefsUtil.USE_LIKE_TYPED_CHANGE, true) == true) {
                    addr = utxo.getOutpoints().get(0).getAddress();
                } else {
                    addr = outputs.getJSONObject(0).getString("address");
                }
                final String ownReceiveAddr;
                if (FormatsUtil.getInstance().isValidBech32(addr)) {
                    ownReceiveAddr = AddressFactory.getInstance(activity).getBIP84(AddressFactory.RECEIVE_CHAIN).getBech32AsString();
                } else if (Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), addr).isP2SHAddress()) {
                    ownReceiveAddr = AddressFactory.getInstance(activity).getBIP49(AddressFactory.RECEIVE_CHAIN).getAddressAsString();
                } else {
                    ownReceiveAddr = AddressFactory.getInstance(activity).get(AddressFactory.RECEIVE_CHAIN).getAddressString();
                }
                Log.d("activity", "receive address:" + ownReceiveAddr);
                long totalAmount = utxo.getValue();
                Log.d("activity", "amount before fee:" + totalAmount);
                Triple<Integer, Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(new Vector(utxo.getOutpoints()));
                BigInteger cpfpFee = FeeUtil.getInstance().estimatedFeeSegwit(outpointTypes.getLeft(), outpointTypes.getMiddle(), outpointTypes.getRight(), 1);
                Log.d("activity", "cpfp fee:" + cpfpFee.longValue());
                p2pkh = outpointTypes.getLeft();
                p2sh_p2wpkh = outpointTypes.getMiddle();
                p2wpkh = outpointTypes.getRight();
                if (totalAmount < (cpfpFee.longValue() + remainingFee)) {
                    Log.d("activity", "selecting additional utxo");
                    Collections.sort(utxos, new UTXO.UTXOComparator());
                    for (UTXO _utxo : utxos) {
                        totalAmount += _utxo.getValue();
                        selectedUTXO.add(_utxo);
                        selected += _utxo.getOutpoints().size();
                        outpointTypes = FeeUtil.getInstance().getOutpointCount(new Vector(utxo.getOutpoints()));
                        p2pkh += outpointTypes.getLeft();
                        p2sh_p2wpkh += outpointTypes.getMiddle();
                        p2wpkh += outpointTypes.getRight();
                        cpfpFee = FeeUtil.getInstance().estimatedFeeSegwit(p2pkh, p2sh_p2wpkh, p2wpkh, 1);
                        if (totalAmount > (cpfpFee.longValue() + remainingFee + SamouraiWallet.bDust.longValue())) {
                            break;
                        }
                    }
                    if (totalAmount < (cpfpFee.longValue() + remainingFee + SamouraiWallet.bDust.longValue())) {
                        handler.post(new Runnable() {

                            public void run() {
                                Toast.makeText(activity, R.string.insufficient_funds, Toast.LENGTH_SHORT).show();
                            }
                        });
                        FeeUtil.getInstance().setSuggestedFee(suggestedFee);
                        return "KO";
                    }
                }
                cpfpFee = cpfpFee.add(BigInteger.valueOf(remainingFee));
                Log.d("activity", "cpfp fee:" + cpfpFee.longValue());
                final List<MyTransactionOutPoint> outPoints = new ArrayList<MyTransactionOutPoint>();
                for (UTXO u : selectedUTXO) {
                    outPoints.addAll(u.getOutpoints());
                }
                long _totalAmount = 0L;
                for (MyTransactionOutPoint outpoint : outPoints) {
                    _totalAmount += outpoint.getValue().longValue();
                }
                Log.d("activity", "checked total amount:" + _totalAmount);
                assert (_totalAmount == totalAmount);
                long amount = totalAmount - cpfpFee.longValue();
                Log.d("activity", "amount after fee:" + amount);
                if (amount < SamouraiWallet.bDust.longValue()) {
                    Log.d("activity", "dust output");
                    Toast.makeText(activity, R.string.cannot_output_dust, Toast.LENGTH_SHORT).show();
                }
                final HashMap<String, BigInteger> receivers = new HashMap<String, BigInteger>();
                receivers.put(ownReceiveAddr, BigInteger.valueOf(amount));
                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) {
                        if (AppUtil.getInstance(activity.getApplicationContext()).isServiceRunning(WebSocketService.class)) {
                            activity.stopService(new Intent(activity.getApplicationContext(), WebSocketService.class));
                        }
                        activity.startService(new Intent(activity.getApplicationContext(), WebSocketService.class));
                        Transaction tx = SendFactory.getInstance(activity).makeTransaction(0, outPoints, receivers);
                        if (tx != null) {
                            tx = SendFactory.getInstance(activity).signTransaction(tx, 0);
                            final String hexTx = new String(Hex.encode(tx.bitcoinSerialize()));
                            Log.d("activity", hexTx);
                            final String strTxHash = tx.getHashAsString();
                            Log.d("activity", strTxHash);
                            boolean isOK = false;
                            try {
                                isOK = PushTx.getInstance(activity).pushTx(hexTx);
                                if (isOK) {
                                    handler.post(new Runnable() {

                                        public void run() {
                                            Toast.makeText(activity, R.string.cpfp_spent, Toast.LENGTH_SHORT).show();
                                            FeeUtil.getInstance().setSuggestedFee(suggestedFee);
                                            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();
                                        }
                                    });
                                    // reset receive index upon tx fail
                                    if (FormatsUtil.getInstance().isValidBech32(addr)) {
                                        int prevIdx = BIP84Util.getInstance(activity).getWallet().getAccount(0).getReceive().getAddrIdx() - 1;
                                        BIP84Util.getInstance(activity).getWallet().getAccount(0).getReceive().setAddrIdx(prevIdx);
                                    } else if (Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), addr).isP2SHAddress()) {
                                        int prevIdx = BIP49Util.getInstance(activity).getWallet().getAccount(0).getReceive().getAddrIdx() - 1;
                                        BIP49Util.getInstance(activity).getWallet().getAccount(0).getReceive().setAddrIdx(prevIdx);
                                    } else {
                                        int prevIdx = HD_WalletFactory.getInstance(activity).get().getAccount(0).getReceive().getAddrIdx() - 1;
                                        HD_WalletFactory.getInstance(activity).get().getAccount(0).getReceive().setAddrIdx(prevIdx);
                                    }
                                }
                            } catch (MnemonicException.MnemonicLengthException | DecoderException | IOException e) {
                                handler.post(new Runnable() {

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

                    public void onClick(DialogInterface dialog, int whichButton) {
                        try {
                            if (Bech32Util.getInstance().isBech32Script(addr)) {
                                int prevIdx = BIP84Util.getInstance(activity).getWallet().getAccount(0).getReceive().getAddrIdx() - 1;
                                BIP84Util.getInstance(activity).getWallet().getAccount(0).getReceive().setAddrIdx(prevIdx);
                            } else if (Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), addr).isP2SHAddress()) {
                                int prevIdx = BIP49Util.getInstance(activity).getWallet().getAccount(0).getReceive().getAddrIdx() - 1;
                                BIP49Util.getInstance(activity).getWallet().getAccount(0).getReceive().setAddrIdx(prevIdx);
                            } else {
                                int prevIdx = HD_WalletFactory.getInstance(activity).get().getAccount(0).getReceive().getAddrIdx() - 1;
                                HD_WalletFactory.getInstance(activity).get().getAccount(0).getReceive().setAddrIdx(prevIdx);
                            }
                        } catch (MnemonicException.MnemonicLengthException | DecoderException | IOException e) {
                            handler.post(new Runnable() {

                                public void run() {
                                    Toast.makeText(activity, e.getMessage(), Toast.LENGTH_SHORT).show();
                                }
                            });
                        } finally {
                            dialog.dismiss();
                        }
                    }
                });
                if (!activity.isFinishing()) {
                    dlg.show();
                }
            } else {
                handler.post(new Runnable() {

                    public void run() {
                        Toast.makeText(activity, R.string.cannot_create_cpfp, Toast.LENGTH_SHORT).show();
                    }
                });
            }
        } catch (final JSONException je) {
            handler.post(new Runnable() {

                public void run() {
                    Toast.makeText(activity, "cpfp:" + je.getMessage(), Toast.LENGTH_SHORT).show();
                }
            });
        }
        FeeUtil.getInstance().setSuggestedFee(suggestedFee);
    } else {
        handler.post(new Runnable() {

            public void run() {
                Toast.makeText(activity, R.string.cpfp_cannot_retrieve_tx, Toast.LENGTH_SHORT).show();
            }
        });
    }
    Looper.loop();
    return "OK";
}
Also used : AlertDialog(android.app.AlertDialog) HashMap(java.util.HashMap) DialogInterface(android.content.DialogInterface) ArrayList(java.util.ArrayList) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) MnemonicException(org.bitcoinj.crypto.MnemonicException) MainActivity2(com.samourai.wallet.MainActivity2) WebSocketService(com.samourai.wallet.service.WebSocketService) Vector(java.util.Vector) Script(org.bitcoinj.script.Script) 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) MnemonicException(org.bitcoinj.crypto.MnemonicException) JSONException(org.json.JSONException) DecoderException(org.bouncycastle.util.encoders.DecoderException) IOException(java.io.IOException) BigInteger(java.math.BigInteger) UTXO(com.samourai.wallet.send.UTXO) DecoderException(org.bouncycastle.util.encoders.DecoderException) JSONObject(org.json.JSONObject) Transaction(org.bitcoinj.core.Transaction) BigInteger(java.math.BigInteger)

Aggregations

AlertDialog (android.app.AlertDialog)2 DialogInterface (android.content.DialogInterface)2 Intent (android.content.Intent)2 MainActivity2 (com.samourai.wallet.MainActivity2)2 MyTransactionOutPoint (com.samourai.wallet.send.MyTransactionOutPoint)2 SuggestedFee (com.samourai.wallet.send.SuggestedFee)2 UTXO (com.samourai.wallet.send.UTXO)2 IOException (java.io.IOException)2 BigInteger (java.math.BigInteger)2 ArrayList (java.util.ArrayList)2 Vector (java.util.Vector)2 Transaction (org.bitcoinj.core.Transaction)2 MnemonicException (org.bitcoinj.crypto.MnemonicException)2 Script (org.bitcoinj.script.Script)2 DecoderException (org.bouncycastle.util.encoders.DecoderException)2 JSONArray (org.json.JSONArray)2 JSONException (org.json.JSONException)2 JSONObject (org.json.JSONObject)2 PaymentAddress (com.samourai.wallet.bip47.rpc.PaymentAddress)1 BIP69OutputComparator (com.samourai.wallet.bip69.BIP69OutputComparator)1