Search in sources :

Example 21 with MyTransactionOutPoint

use of com.samourai.wallet.send.MyTransactionOutPoint 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 22 with MyTransactionOutPoint

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

Example 23 with MyTransactionOutPoint

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

the class RicochetMeta method getHop0Tx.

private Transaction getHop0Tx(List<UTXO> utxos, long spendAmount, String destination, long fee, boolean samouraiFeeViaBIP47, long nTimeLock) {
    List<MyTransactionOutPoint> unspent = new ArrayList<MyTransactionOutPoint>();
    long totalValueSelected = 0L;
    for (UTXO u : utxos) {
        totalValueSelected += u.getValue();
        unspent.addAll(u.getOutpoints());
    }
    // Log.d("RicochetMeta", "spendAmount:" + spendAmount);
    // Log.d("RicochetMeta", "fee:" + fee);
    // Log.d("RicochetMeta", "totalValueSelected:" + totalValueSelected);
    BigInteger samouraiFeeAmount = samouraiFeeAmountV2;
    long changeAmount = totalValueSelected - (spendAmount + fee);
    // Log.d("RicochetMeta", "changeAmount:" + changeAmount);
    HashMap<String, BigInteger> receivers = new HashMap<String, BigInteger>();
    if (changeAmount > 0L) {
        String change_address = BIP84Util.getInstance(context).getAddressAt(AddressFactory.CHANGE_CHAIN, BIP84Util.getInstance(context).getWallet().getAccount(0).getChange().getAddrIdx()).getBech32AsString();
        receivers.put(change_address, BigInteger.valueOf(changeAmount));
    }
    if (samouraiFeeViaBIP47) {
        // Samourai fee paid in the hops
        receivers.put(destination, BigInteger.valueOf(spendAmount));
    } else {
        if (nTimeLock > 0L) {
            receivers.put(SamouraiWallet.getInstance().isTestNet() ? TESTNET_NLOCKTIME_SAMOURAI_RICOCHET_TX_FEE_ADDRESS : SAMOURAI_NLOCKTIME_RICOCHET_TX_FEE_ADDRESS, samouraiFeeAmount);
        } else {
            receivers.put(SamouraiWallet.getInstance().isTestNet() ? TESTNET_SAMOURAI_RICOCHET_TX_FEE_ADDRESS : SAMOURAI_RICOCHET_TX_FEE_ADDRESS, samouraiFeeAmount);
        }
        receivers.put(destination, BigInteger.valueOf(spendAmount - samouraiFeeAmount.longValue()));
    }
    Transaction tx = SendFactory.getInstance(context).makeTransaction(0, unspent, receivers);
    if (nTimeLock > 0L) {
        tx.setLockTime(nTimeLock);
    }
    tx = SendFactory.getInstance(context).signTransaction(tx, 0);
    return tx;
}
Also used : UTXO(com.samourai.wallet.send.UTXO) Transaction(org.bitcoinj.core.Transaction) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) BigInteger(java.math.BigInteger) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint)

Example 24 with MyTransactionOutPoint

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

the class APIFactory method parseUnspentOutputs.

private synchronized boolean parseUnspentOutputs(String unspents) {
    if (unspents != null) {
        try {
            JSONObject jsonObj = new JSONObject(unspents);
            if (jsonObj == null || !jsonObj.has("unspent_outputs")) {
                return false;
            }
            JSONArray utxoArray = jsonObj.getJSONArray("unspent_outputs");
            if (utxoArray == null || utxoArray.length() == 0) {
                return false;
            }
            for (int i = 0; i < utxoArray.length(); i++) {
                JSONObject outDict = utxoArray.getJSONObject(i);
                byte[] hashBytes = Hex.decode((String) outDict.get("tx_hash"));
                Sha256Hash txHash = Sha256Hash.wrap(hashBytes);
                int txOutputN = ((Number) outDict.get("tx_output_n")).intValue();
                BigInteger value = BigInteger.valueOf(((Number) outDict.get("value")).longValue());
                String script = (String) outDict.get("script");
                byte[] scriptBytes = Hex.decode(script);
                int confirmations = ((Number) outDict.get("confirmations")).intValue();
                String path = null;
                try {
                    String address = null;
                    if (Bech32Util.getInstance().isBech32Script(script)) {
                        address = Bech32Util.getInstance().getAddressFromScript(script);
                    } else {
                        address = new Script(scriptBytes).getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
                    }
                    if (outDict.has("xpub")) {
                        JSONObject xpubObj = (JSONObject) outDict.get("xpub");
                        path = (String) xpubObj.get("path");
                        String m = (String) xpubObj.get("m");
                        unspentPaths.put(address, path);
                        if (m.equals(BIP49Util.getInstance(context).getWallet().getAccount(0).xpubstr())) {
                            // assume account 0
                            unspentBIP49.put(address, 0);
                        } else if (m.equals(BIP84Util.getInstance(context).getWallet().getAccount(0).xpubstr())) {
                            // assume account 0
                            unspentBIP84.put(address, 0);
                        } else {
                            unspentAccounts.put(address, AddressFactory.getInstance(context).xpub2account().get(m));
                        }
                    } else if (outDict.has("pubkey")) {
                        int idx = BIP47Meta.getInstance().getIdx4AddrLookup().get(outDict.getString("pubkey"));
                        BIP47Meta.getInstance().getIdx4AddrLookup().put(address, idx);
                        String pcode = BIP47Meta.getInstance().getPCode4AddrLookup().get(outDict.getString("pubkey"));
                        BIP47Meta.getInstance().getPCode4AddrLookup().put(address, pcode);
                        debug("APIFactory", outDict.getString("pubkey") + "," + pcode);
                        debug("APIFactory", outDict.getString("pubkey") + "," + idx);
                    } else {
                        ;
                    }
                    // Construct the output
                    MyTransactionOutPoint outPoint = new MyTransactionOutPoint(txHash, txOutputN, value, scriptBytes, address);
                    outPoint.setConfirmations(confirmations);
                    if (utxos.containsKey(script)) {
                        utxos.get(script).getOutpoints().add(outPoint);
                    } else {
                        UTXO utxo = new UTXO();
                        utxo.getOutpoints().add(outPoint);
                        utxo.setPath(path);
                        utxos.put(script, utxo);
                    }
                    if (Bech32Util.getInstance().isBech32Script(script)) {
                        UTXOFactory.getInstance().addP2WPKH(txHash.toString(), txOutputN, script, utxos.get(script));
                    } else if (Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) {
                        UTXOFactory.getInstance().addP2SH_P2WPKH(txHash.toString(), txOutputN, script, utxos.get(script));
                    } else {
                        UTXOFactory.getInstance().addP2PKH(txHash.toString(), txOutputN, script, utxos.get(script));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            long amount = 0L;
            for (String key : utxos.keySet()) {
                for (MyTransactionOutPoint out : utxos.get(key).getOutpoints()) {
                    debug("APIFactory", "utxo:" + out.getAddress() + "," + out.getValue());
                    debug("APIFactory", "utxo:" + utxos.get(key).getPath());
                    amount += out.getValue().longValue();
                }
            }
            debug("APIFactory", "utxos by value (post-parse):" + amount);
            return true;
        } catch (JSONException je) {
            ;
        }
    }
    return false;
}
Also used : Script(org.bitcoinj.script.Script) Sha256Hash(org.bitcoinj.core.Sha256Hash) JSONArray(org.json.JSONArray) JSONException(org.json.JSONException) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) TransactionOutPoint(org.bitcoinj.core.TransactionOutPoint) NotSecp256k1Exception(com.samourai.wallet.bip47.rpc.NotSecp256k1Exception) JSONException(org.json.JSONException) AddressFormatException(org.bitcoinj.core.AddressFormatException) NoSuchAlgorithmException(java.security.NoSuchAlgorithmException) InvalidKeyException(java.security.InvalidKeyException) InvalidKeySpecException(java.security.spec.InvalidKeySpecException) MnemonicException(org.bitcoinj.crypto.MnemonicException) DecryptionException(com.samourai.wallet.crypto.DecryptionException) IOException(java.io.IOException) NoSuchProviderException(java.security.NoSuchProviderException) UTXO(com.samourai.wallet.send.UTXO) BlockedUTXO(com.samourai.wallet.send.BlockedUTXO) JSONObject(org.json.JSONObject) BigInteger(java.math.BigInteger)

Example 25 with MyTransactionOutPoint

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

the class APIFactory method parseUnspentOutputsForSweep.

private synchronized UTXO parseUnspentOutputsForSweep(String unspents) {
    UTXO utxo = null;
    if (unspents != null) {
        try {
            JSONObject jsonObj = new JSONObject(unspents);
            if (jsonObj == null || !jsonObj.has("unspent_outputs")) {
                return null;
            }
            JSONArray utxoArray = jsonObj.getJSONArray("unspent_outputs");
            if (utxoArray == null || utxoArray.length() == 0) {
                return null;
            }
            for (int i = 0; i < utxoArray.length(); i++) {
                JSONObject outDict = utxoArray.getJSONObject(i);
                byte[] hashBytes = Hex.decode((String) outDict.get("tx_hash"));
                Sha256Hash txHash = Sha256Hash.wrap(hashBytes);
                int txOutputN = ((Number) outDict.get("tx_output_n")).intValue();
                BigInteger value = BigInteger.valueOf(((Number) outDict.get("value")).longValue());
                String script = (String) outDict.get("script");
                byte[] scriptBytes = Hex.decode(script);
                int confirmations = ((Number) outDict.get("confirmations")).intValue();
                try {
                    String address = null;
                    if (Bech32Util.getInstance().isBech32Script(script)) {
                        address = Bech32Util.getInstance().getAddressFromScript(script);
                        debug("address parsed:", address);
                    } else {
                        address = new Script(scriptBytes).getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
                    }
                    // Construct the output
                    MyTransactionOutPoint outPoint = new MyTransactionOutPoint(txHash, txOutputN, value, scriptBytes, address);
                    outPoint.setConfirmations(confirmations);
                    if (utxo == null) {
                        utxo = new UTXO();
                    }
                    utxo.getOutpoints().add(outPoint);
                } catch (Exception e) {
                    ;
                }
            }
        } catch (JSONException je) {
            ;
        }
    }
    return utxo;
}
Also used : Script(org.bitcoinj.script.Script) Sha256Hash(org.bitcoinj.core.Sha256Hash) JSONArray(org.json.JSONArray) JSONException(org.json.JSONException) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) TransactionOutPoint(org.bitcoinj.core.TransactionOutPoint) NotSecp256k1Exception(com.samourai.wallet.bip47.rpc.NotSecp256k1Exception) JSONException(org.json.JSONException) AddressFormatException(org.bitcoinj.core.AddressFormatException) NoSuchAlgorithmException(java.security.NoSuchAlgorithmException) InvalidKeyException(java.security.InvalidKeyException) InvalidKeySpecException(java.security.spec.InvalidKeySpecException) MnemonicException(org.bitcoinj.crypto.MnemonicException) DecryptionException(com.samourai.wallet.crypto.DecryptionException) IOException(java.io.IOException) NoSuchProviderException(java.security.NoSuchProviderException) UTXO(com.samourai.wallet.send.UTXO) BlockedUTXO(com.samourai.wallet.send.BlockedUTXO) JSONObject(org.json.JSONObject) BigInteger(java.math.BigInteger)

Aggregations

MyTransactionOutPoint (com.samourai.wallet.send.MyTransactionOutPoint)32 UTXO (com.samourai.wallet.send.UTXO)28 HashMap (java.util.HashMap)19 ArrayList (java.util.ArrayList)18 Transaction (org.bitcoinj.core.Transaction)16 BlockedUTXO (com.samourai.wallet.send.BlockedUTXO)12 IOException (java.io.IOException)12 BigInteger (java.math.BigInteger)12 ECKey (org.bitcoinj.core.ECKey)12 TransactionOutPoint (org.bitcoinj.core.TransactionOutPoint)12 MnemonicException (org.bitcoinj.crypto.MnemonicException)11 JSONException (org.json.JSONException)11 JSONObject (org.json.JSONObject)11 TransactionInput (org.bitcoinj.core.TransactionInput)10 Script (org.bitcoinj.script.Script)9 Intent (android.content.Intent)8 SegwitAddress (com.samourai.wallet.segwit.SegwitAddress)8 AlertDialog (android.app.AlertDialog)7 DecryptionException (com.samourai.wallet.crypto.DecryptionException)7 DialogInterface (android.content.DialogInterface)6