Search in sources :

Example 16 with UTXOCoin

use of com.samourai.wallet.utxos.models.UTXOCoin in project samourai-wallet-android by Samourai-Wallet.

the class UTXOSActivity method applyFilters.

private void applyFilters() {
    // ArrayList will store UTXOs that or filtered based on spending status
    ArrayList<UTXOCoin> filteredStatus = new ArrayList<>();
    for (UTXOCoin model : unFilteredUTXOS) {
        if (statusUnSpendable) {
            if (model.doNotSpend) {
                if (!filteredStatus.contains(model)) {
                    filteredStatus.add(model);
                }
            }
        }
        if (statusSpendable) {
            if (!model.doNotSpend) {
                if (!filteredStatus.contains(model)) {
                    filteredStatus.add(model);
                }
            }
        }
    }
    // ArrayList will store UTXOs that or filtered based on Address types
    // types includes SEGWIT_NATIVE,SEGWIT_COMPAT,LEGACY
    List<UTXOCoin> filteredAddress = new ArrayList<>(filteredStatus);
    // counters to check spendables and unspendables
    // sections will be added based on this counters
    int unspendables = 0;
    int spendables = 0;
    for (UTXOCoin model : filteredStatus) {
        if (model.doNotSpend) {
            unspendables = unspendables + 1;
        } else {
            spendables = spendables + 1;
        }
        UTXOUtil.AddressTypes type = UTXOUtil.getAddressType(model.address);
        switch(type) {
            case LEGACY:
                if (!addressFilterLegacy) {
                    filteredAddress.remove(model);
                }
                break;
            case SEGWIT_COMPAT:
                {
                    if (!addressFilterSegwitCompat) {
                        filteredAddress.remove(model);
                    }
                    break;
                }
            case SEGWIT_NATIVE:
                {
                    if (!addressFilterSegwitNat) {
                        filteredAddress.remove(model);
                    }
                    break;
                }
        }
    }
    if (utxoSortOrder) {
        // Ascending order sorting based on the UTXO amount
        Collections.sort(filteredAddress, (model, t1) -> Long.compare(model.amount, t1.amount));
    } else {
        // Descending  order sorting based on the UTXO amount
        Collections.sort(filteredAddress, (model, t1) -> Long.compare(t1.amount, model.amount));
    }
    // Sectioned dataset for RecyclerView adapter
    // here array will split based on spending status
    List<UTXOCoin> sectioned = new ArrayList<>();
    if (spendables > 0) {
        UTXOCoinSegment active = new UTXOCoinSegment(null, null);
        active.id = 0;
        active.isActive = true;
        sectioned.add(active);
    }
    for (UTXOCoin models : filteredAddress) {
        if (!models.doNotSpend) {
            models.id = filteredAddress.indexOf(models) + 1;
            sectioned.add(models);
        }
    }
    if (unspendables > 0) {
        UTXOCoinSegment doNotSpend = new UTXOCoinSegment(null, null);
        doNotSpend.id = filteredAddress.size() + 1;
        doNotSpend.isActive = false;
        doNotSpend.hash = "not_active";
        sectioned.add(doNotSpend);
    }
    for (UTXOCoin models : filteredAddress) {
        if (models.doNotSpend) {
            models.id = filteredAddress.indexOf(models) + 1;
            sectioned.add(models);
        }
    }
    this.adapter.updateList(sectioned);
}
Also used : UTXOCoinSegment(com.samourai.wallet.utxos.models.UTXOCoinSegment) UTXOCoin(com.samourai.wallet.utxos.models.UTXOCoin) ArrayList(java.util.ArrayList) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint)

Example 17 with UTXOCoin

use of com.samourai.wallet.utxos.models.UTXOCoin in project samourai-wallet-android by Samourai-Wallet.

the class UTXOSActivity method getUTXOs.

private Observable<Map<String, Object>> getUTXOs() {
    return Observable.fromCallable(() -> {
        long totalP2WPKH = 0L;
        long totalBlocked = 0L;
        long totalP2PKH = 0L;
        long totalP2SH_P2WPKH = 0L;
        noteAmounts.clear();
        tagAmounts.clear();
        Map<String, Object> dataSet = new HashMap<>();
        List<UTXO> utxos = null;
        if (account == WhirlpoolMeta.getInstance(getApplicationContext()).getWhirlpoolPostmix()) {
            utxos = APIFactory.getInstance(getApplicationContext()).getUtxosPostMix(false);
        } else {
            // utxos = APIFactory.getInstance(getApplicationContext()).getUtxosWithLocalCache(false, true);
            utxos = APIFactory.getInstance(getApplicationContext()).getUtxos(false);
        }
        long amount = 0L;
        for (UTXO utxo : utxos) {
            for (MyTransactionOutPoint out : utxo.getOutpoints()) {
                debug("UTXOSActivity", "utxo:" + out.getAddress() + "," + out.getValue());
                debug("UTXOSActivity", "utxo:" + utxo.getPath());
                amount += out.getValue().longValue();
            }
        }
        ArrayList<UTXOCoin> items = new ArrayList<>();
        for (UTXO utxo : utxos) {
            for (MyTransactionOutPoint outpoint : utxo.getOutpoints()) {
                UTXOCoin displayData = new UTXOCoin(outpoint, utxo);
                if (BlockedUTXO.getInstance().contains(outpoint.getTxHash().toString(), outpoint.getTxOutputN())) {
                    displayData.doNotSpend = true;
                    totalBlocked += displayData.amount;
                } else if (BlockedUTXO.getInstance().containsPostMix(outpoint.getTxHash().toString(), outpoint.getTxOutputN())) {
                    displayData.doNotSpend = true;
                    // Log.d("UTXOActivity", "marked as do not spend");
                    totalBlocked += displayData.amount;
                } else {
                    // Log.d("UTXOActivity", "unmarked");
                    if (FormatsUtil.getInstance().isValidBech32(displayData.address)) {
                        totalP2WPKH += displayData.amount;
                    } else if (Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), displayData.address).isP2SHAddress()) {
                        totalP2SH_P2WPKH += displayData.amount;
                    } else {
                        totalP2PKH += displayData.amount;
                    }
                }
                if (UTXOUtil.getInstance().get(outpoint.getTxHash().toString(), outpoint.getTxOutputN()) != null) {
                    List<String> tags = UTXOUtil.getInstance().get(outpoint.getTxHash().toString(), outpoint.getTxOutputN());
                    for (String tag : tags) {
                        if (tagAmounts.containsKey(tag.toLowerCase())) {
                            long val = tagAmounts.get(tag.toLowerCase());
                            val += displayData.amount;
                            tagAmounts.put(tag.toLowerCase(), val);
                        } else {
                            tagAmounts.put(tag.toLowerCase(), displayData.amount);
                        }
                    }
                }
                if (UTXOUtil.getInstance().getNote(outpoint.getTxHash().toString()) != null) {
                    String note = UTXOUtil.getInstance().getNote(outpoint.getTxHash().toString());
                    if (noteAmounts.containsKey(note.toLowerCase())) {
                        long val = noteAmounts.get(note.toLowerCase());
                        val += displayData.amount;
                        noteAmounts.put(note.toLowerCase(), val);
                    } else {
                        noteAmounts.put(note.toLowerCase(), displayData.amount);
                    }
                }
                items.add(displayData);
            }
        }
        dataSet.put("totalP2WPKH", totalP2WPKH);
        dataSet.put("totalBlocked", totalBlocked);
        dataSet.put("totalP2SH_P2WPKH", totalP2SH_P2WPKH);
        dataSet.put("totalP2PKH", totalP2PKH);
        dataSet.put("utxos", items);
        return dataSet;
    });
}
Also used : UTXO(com.samourai.wallet.send.UTXO) BlockedUTXO(com.samourai.wallet.send.BlockedUTXO) UTXOCoin(com.samourai.wallet.utxos.models.UTXOCoin) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint)

Example 18 with UTXOCoin

use of com.samourai.wallet.utxos.models.UTXOCoin in project samourai-wallet-android by Samourai-Wallet.

the class UTXOSActivity method getPreSelected.

public String getPreSelected() {
    ArrayList<UTXOCoin> utxos = new ArrayList<>();
    for (UTXOCoin utxo : this.filteredUTXOs) {
        if (utxo.isSelected) {
            utxos.add(utxo);
        }
    }
    String id = null;
    if (utxos.size() > 0) {
        id = UUID.randomUUID().toString();
        PreSelectUtil.getInstance().add(id, utxos);
    }
    return id;
}
Also used : UTXOCoin(com.samourai.wallet.utxos.models.UTXOCoin) ArrayList(java.util.ArrayList)

Example 19 with UTXOCoin

use of com.samourai.wallet.utxos.models.UTXOCoin in project samourai-wallet-android by Samourai-Wallet.

the class SendActivity method setBalance.

private void setBalance() {
    if (preselectedUTXOs != null && preselectedUTXOs.size() > 0) {
        long amount = 0;
        for (UTXOCoin utxo : preselectedUTXOs) {
            amount += utxo.amount;
        }
        balance = amount;
    } else {
        try {
            if (account == WhirlpoolMeta.getInstance(SendActivity.this).getWhirlpoolPostmix()) {
                balance = APIFactory.getInstance(SendActivity.this).getXpubPostMixBalance();
            } else {
                Long tempBalance = APIFactory.getInstance(SendActivity.this).getXpubAmounts().get(HD_WalletFactory.getInstance(SendActivity.this).get().getAccount(0).xpubstr());
                if (tempBalance != 0L) {
                    balance = tempBalance;
                }
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } catch (MnemonicException.MnemonicLengthException mle) {
            mle.printStackTrace();
        } catch (java.lang.NullPointerException npe) {
            npe.printStackTrace();
        }
    }
    final String strAmount;
    NumberFormat nf = NumberFormat.getInstance(Locale.US);
    nf.setMaximumFractionDigits(8);
    nf.setMinimumFractionDigits(1);
    nf.setMinimumIntegerDigits(1);
    strAmount = nf.format(balance / 1e8);
    if (account == 0) {
        tvMaxAmount.setOnClickListener(view -> {
            btcEditText.setText(strAmount);
        });
    }
    tvMaxAmount.setOnLongClickListener(view -> {
        setBalance();
        return true;
    });
    tvMaxAmount.setText(strAmount + " " + getDisplayUnits());
    if (balance == 0L && !APIFactory.getInstance(getApplicationContext()).walletInit) {
        // some time, user may navigate to this activity even before wallet initialization completes
        // so we will set a delay to reload balance info
        Disposable disposable = Completable.timer(700, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()).subscribe(this::setBalance);
        compositeDisposables.add(disposable);
        if (!shownWalletLoadingMessage) {
            Snackbar.make(tvMaxAmount.getRootView(), "Please wait... your wallet is still loading ", Snackbar.LENGTH_LONG).show();
            shownWalletLoadingMessage = true;
        }
    }
}
Also used : CompositeDisposable(io.reactivex.disposables.CompositeDisposable) Disposable(io.reactivex.disposables.Disposable) MnemonicException(org.bitcoinj.crypto.MnemonicException) UTXOCoin(com.samourai.wallet.utxos.models.UTXOCoin) IOException(java.io.IOException) NumberFormat(java.text.NumberFormat)

Example 20 with UTXOCoin

use of com.samourai.wallet.utxos.models.UTXOCoin in project samourai-wallet-android by Samourai-Wallet.

the class SendActivity method prepareSpend.

private boolean prepareSpend() {
    restoreChangeIndexes();
    double btc_amount = 0.0;
    try {
        btc_amount = NumberFormat.getInstance(Locale.US).parse(btcEditText.getText().toString().trim()).doubleValue();
    // Log.i("SendFragment", "amount entered:" + btc_amount);
    } catch (NumberFormatException nfe) {
        btc_amount = 0.0;
    } catch (ParseException pe) {
        btc_amount = 0.0;
    }
    double dAmount = btc_amount;
    amount = (long) (Math.round(dAmount * 1e8));
    if (selectedCahootsType == SelectCahootsType.type.STOWAWAY) {
        setButtonForStowaway(true);
        return true;
    } else {
        setButtonForStowaway(false);
    }
    address = strDestinationBTCAddress == null ? toAddressEditText.getText().toString().trim() : strDestinationBTCAddress;
    if (account == WhirlpoolMeta.getInstance(SendActivity.this).getWhirlpoolPostmix()) {
        changeType = 84;
    } else if (PrefsUtil.getInstance(SendActivity.this).getValue(PrefsUtil.USE_LIKE_TYPED_CHANGE, true) == false) {
        changeType = 84;
    } else if (FormatsUtil.getInstance().isValidBech32(address) || Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) {
        changeType = FormatsUtil.getInstance().isValidBech32(address) ? 84 : 49;
    } else {
        changeType = 44;
    }
    receivers = new HashMap<String, BigInteger>();
    receivers.put(address, BigInteger.valueOf(amount));
    if (account == WhirlpoolMeta.getInstance(SendActivity.this).getWhirlpoolPostmix()) {
        change_index = idxBIP84PostMixInternal;
    } else if (changeType == 84) {
        change_index = idxBIP84Internal;
    } else if (changeType == 49) {
        change_index = idxBIP49Internal;
    } else {
        change_index = idxBIP44Internal;
    }
    // if possible, get UTXO by input 'type': p2pkh, p2sh-p2wpkh or p2wpkh, else get all UTXO
    long neededAmount = 0L;
    if (FormatsUtil.getInstance().isValidBech32(address) || account == WhirlpoolMeta.getInstance(SendActivity.this).getWhirlpoolPostmix()) {
        neededAmount += FeeUtil.getInstance().estimatedFeeSegwit(0, 0, UTXOFactory.getInstance().getCountP2WPKH(), 4).longValue();
    // Log.d("SendActivity", "segwit:" + neededAmount);
    } else if (Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) {
        neededAmount += FeeUtil.getInstance().estimatedFeeSegwit(0, UTXOFactory.getInstance().getCountP2SH_P2WPKH(), 0, 4).longValue();
    // Log.d("SendActivity", "segwit:" + neededAmount);
    } else {
        neededAmount += FeeUtil.getInstance().estimatedFeeSegwit(UTXOFactory.getInstance().getCountP2PKH(), 0, 4).longValue();
    // Log.d("SendActivity", "p2pkh:" + neededAmount);
    }
    neededAmount += amount;
    neededAmount += SamouraiWallet.bDust.longValue();
    // get all UTXO
    List<UTXO> utxos = new ArrayList<>();
    if (preselectedUTXOs != null && preselectedUTXOs.size() > 0) {
        // sort in descending order by value
        for (UTXOCoin utxoCoin : preselectedUTXOs) {
            UTXO u = new UTXO();
            List<MyTransactionOutPoint> outs = new ArrayList<MyTransactionOutPoint>();
            outs.add(utxoCoin.getOutPoint());
            u.setOutpoints(outs);
            utxos.add(u);
        }
    } else {
        utxos = SpendUtil.getUTXOS(SendActivity.this, address, neededAmount, account);
    }
    List<UTXO> utxosP2WPKH = new ArrayList<UTXO>(UTXOFactory.getInstance().getAllP2WPKH().values());
    List<UTXO> utxosP2SH_P2WPKH = new ArrayList<UTXO>(UTXOFactory.getInstance().getAllP2SH_P2WPKH().values());
    List<UTXO> utxosP2PKH = new ArrayList<UTXO>(UTXOFactory.getInstance().getAllP2PKH().values());
    if ((preselectedUTXOs == null || preselectedUTXOs.size() == 0) && account == WhirlpoolMeta.getInstance(SendActivity.this).getWhirlpoolPostmix()) {
        utxos = new ArrayList<UTXO>(UTXOFactory.getInstance().getAllPostMix().values());
        utxosP2WPKH = new ArrayList<UTXO>(UTXOFactory.getInstance().getAllPostMix().values());
        utxosP2PKH.clear();
        utxosP2SH_P2WPKH.clear();
    }
    selectedUTXO = new ArrayList<UTXO>();
    long totalValueSelected = 0L;
    long change = 0L;
    BigInteger fee = null;
    boolean canDoBoltzmann = true;
    // insufficient funds
    if (amount > balance) {
        Toast.makeText(SendActivity.this, R.string.insufficient_funds, Toast.LENGTH_SHORT).show();
    }
    if (preselectedUTXOs != null) {
        canDoBoltzmann = false;
        SPEND_TYPE = SPEND_SIMPLE;
    } else // entire balance (can only be simple spend)
    if (amount == balance) {
        // make sure we are using simple spend
        SPEND_TYPE = SPEND_SIMPLE;
        canDoBoltzmann = false;
        // Log.d("SendActivity", "amount == balance");
        // take all utxos, deduct fee
        selectedUTXO.addAll(utxos);
        for (UTXO u : selectedUTXO) {
            totalValueSelected += u.getValue();
        }
    // Log.d("SendActivity", "balance:" + balance);
    // Log.d("SendActivity", "total value selected:" + totalValueSelected);
    } else {
        ;
    }
    org.apache.commons.lang3.tuple.Pair<ArrayList<MyTransactionOutPoint>, ArrayList<TransactionOutput>> pair = null;
    if (SPEND_TYPE == SPEND_RICOCHET) {
        boolean samouraiFeeViaBIP47 = false;
        if (BIP47Meta.getInstance().getOutgoingStatus(BIP47Meta.strSamouraiDonationPCode) == BIP47Meta.STATUS_SENT_CFM) {
            samouraiFeeViaBIP47 = true;
        }
        ricochetJsonObj = RicochetMeta.getInstance(SendActivity.this).script(amount, FeeUtil.getInstance().getSuggestedFee().getDefaultPerKB().longValue(), address, 4, strPCode, samouraiFeeViaBIP47, ricochetStaggeredDelivery.isChecked());
        if (ricochetJsonObj != null) {
            try {
                long totalAmount = ricochetJsonObj.getLong("total_spend");
                if (totalAmount > balance) {
                    Toast.makeText(SendActivity.this, R.string.insufficient_funds, Toast.LENGTH_SHORT).show();
                    ricochetHopsSwitch.setChecked(false);
                    return false;
                }
                long hop0Fee = ricochetJsonObj.getJSONArray("hops").getJSONObject(0).getLong("fee");
                long perHopFee = ricochetJsonObj.getJSONArray("hops").getJSONObject(0).getLong("fee_per_hop");
                long ricochetFee = hop0Fee + (RicochetMeta.defaultNbHops * perHopFee);
                if (selectedCahootsType == SelectCahootsType.type.NONE) {
                    tvTotalFee.setText(Coin.valueOf(ricochetFee).toPlainString().concat(" BTC"));
                } else {
                    tvTotalFee.setText("__");
                }
                ricochetMessage = getText(R.string.ricochet_spend1) + " " + address + " " + getText(R.string.ricochet_spend2) + " " + Coin.valueOf(totalAmount).toPlainString() + " " + getText(R.string.ricochet_spend3);
                btnSend.setText("send ".concat(String.format(Locale.ENGLISH, "%.8f", getBtcValue((double) totalAmount)).concat(" BTC")));
                return true;
            } catch (JSONException je) {
                return false;
            }
        }
        return true;
    } else if (SPEND_TYPE == SPEND_BOLTZMANN) {
        Log.d("SendActivity", "needed amount:" + neededAmount);
        List<UTXO> _utxos1 = null;
        List<UTXO> _utxos2 = null;
        long valueP2WPKH = UTXOFactory.getInstance().getTotalP2WPKH();
        long valueP2SH_P2WPKH = UTXOFactory.getInstance().getTotalP2SH_P2WPKH();
        long valueP2PKH = UTXOFactory.getInstance().getTotalP2PKH();
        if (account == WhirlpoolMeta.getInstance(SendActivity.this).getWhirlpoolPostmix()) {
            valueP2WPKH = UTXOFactory.getInstance().getTotalPostMix();
            valueP2SH_P2WPKH = 0L;
            valueP2PKH = 0L;
            utxosP2SH_P2WPKH.clear();
            utxosP2PKH.clear();
        }
        Log.d("SendActivity", "value P2WPKH:" + valueP2WPKH);
        Log.d("SendActivity", "value P2SH_P2WPKH:" + valueP2SH_P2WPKH);
        Log.d("SendActivity", "value P2PKH:" + valueP2PKH);
        boolean selectedP2WPKH = false;
        boolean selectedP2SH_P2WPKH = false;
        boolean selectedP2PKH = false;
        if ((valueP2WPKH > (neededAmount * 2)) && account == WhirlpoolMeta.getInstance(SendActivity.this).getWhirlpoolPostmix()) {
            Log.d("SendActivity", "set 1 P2WPKH 2x");
            _utxos1 = utxosP2WPKH;
            selectedP2WPKH = true;
        } else if ((valueP2WPKH > (neededAmount * 2)) && FormatsUtil.getInstance().isValidBech32(address)) {
            Log.d("SendActivity", "set 1 P2WPKH 2x");
            _utxos1 = utxosP2WPKH;
            selectedP2WPKH = true;
        } else if (!FormatsUtil.getInstance().isValidBech32(address) && (valueP2SH_P2WPKH > (neededAmount * 2)) && Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) {
            Log.d("SendActivity", "set 1 P2SH_P2WPKH 2x");
            _utxos1 = utxosP2SH_P2WPKH;
            selectedP2SH_P2WPKH = true;
        } else if (!FormatsUtil.getInstance().isValidBech32(address) && (valueP2PKH > (neededAmount * 2)) && !Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) {
            Log.d("SendActivity", "set 1 P2PKH 2x");
            _utxos1 = utxosP2PKH;
            selectedP2PKH = true;
        } else if (valueP2WPKH > (neededAmount * 2)) {
            Log.d("SendActivity", "set 1 P2WPKH 2x");
            _utxos1 = utxosP2WPKH;
            selectedP2WPKH = true;
        } else if (valueP2SH_P2WPKH > (neededAmount * 2)) {
            Log.d("SendActivity", "set 1 P2SH_P2WPKH 2x");
            _utxos1 = utxosP2SH_P2WPKH;
            selectedP2SH_P2WPKH = true;
        } else if (valueP2PKH > (neededAmount * 2)) {
            Log.d("SendActivity", "set 1 P2PKH 2x");
            _utxos1 = utxosP2PKH;
            selectedP2PKH = true;
        } else {
            ;
        }
        if (_utxos1 == null || _utxos1.size() == 0) {
            if (valueP2SH_P2WPKH > neededAmount) {
                Log.d("SendActivity", "set 1 P2SH_P2WPKH");
                _utxos1 = utxosP2SH_P2WPKH;
                selectedP2SH_P2WPKH = true;
            } else if (valueP2WPKH > neededAmount) {
                Log.d("SendActivity", "set 1 P2WPKH");
                _utxos1 = utxosP2WPKH;
                selectedP2WPKH = true;
            } else if (valueP2PKH > neededAmount) {
                Log.d("SendActivity", "set 1 P2PKH");
                _utxos1 = utxosP2PKH;
                selectedP2PKH = true;
            } else {
                ;
            }
        }
        if (_utxos1 != null && _utxos1.size() > 0) {
            if (!selectedP2SH_P2WPKH && valueP2SH_P2WPKH > neededAmount) {
                Log.d("SendActivity", "set 2 P2SH_P2WPKH");
                _utxos2 = utxosP2SH_P2WPKH;
                selectedP2SH_P2WPKH = true;
            }
            if (!selectedP2SH_P2WPKH && !selectedP2WPKH && valueP2WPKH > neededAmount) {
                Log.d("SendActivity", "set 2 P2WPKH");
                _utxos2 = utxosP2WPKH;
                selectedP2WPKH = true;
            }
            if (!selectedP2SH_P2WPKH && !selectedP2WPKH && !selectedP2PKH && valueP2PKH > neededAmount) {
                Log.d("SendActivity", "set 2 P2PKH");
                _utxos2 = utxosP2PKH;
                selectedP2PKH = true;
            } else {
                ;
            }
        }
        if ((_utxos1 == null || _utxos1.size() == 0) && (_utxos2 == null || _utxos2.size() == 0)) {
            // can't do boltzmann, revert to SPEND_SIMPLE
            canDoBoltzmann = false;
            SPEND_TYPE = SPEND_SIMPLE;
        } else {
            Log.d("SendActivity", "boltzmann spend");
            Collections.shuffle(_utxos1);
            if (_utxos2 != null && _utxos2.size() > 0) {
                Collections.shuffle(_utxos2);
            }
            // boltzmann spend (STONEWALL)
            pair = SendFactory.getInstance(SendActivity.this).boltzmann(_utxos1, _utxos2, BigInteger.valueOf(amount), address, account);
            if (pair == null) {
                // can't do boltzmann, revert to SPEND_SIMPLE
                canDoBoltzmann = false;
                restoreChangeIndexes();
                SPEND_TYPE = SPEND_SIMPLE;
            } else {
                canDoBoltzmann = true;
            }
        }
    } else {
        ;
    }
    if (SPEND_TYPE == SPEND_SIMPLE && amount == balance && preselectedUTXOs == null) {
        // do nothing, utxo selection handles above
        ;
    } else // simple spend (less than balance)
    if (SPEND_TYPE == SPEND_SIMPLE) {
        List<UTXO> _utxos = utxos;
        // sort in ascending order by value
        Collections.sort(_utxos, new UTXO.UTXOComparator());
        Collections.reverse(_utxos);
        // get smallest 1 UTXO > than spend + fee + dust
        for (UTXO u : _utxos) {
            Triple<Integer, Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(new Vector(u.getOutpoints()));
            if (u.getValue() >= (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFeeSegwit(outpointTypes.getLeft(), outpointTypes.getMiddle(), outpointTypes.getRight(), 2).longValue())) {
                selectedUTXO.add(u);
                totalValueSelected += u.getValue();
                Log.d("SendActivity", "spend type:" + SPEND_TYPE);
                Log.d("SendActivity", "single output");
                Log.d("SendActivity", "amount:" + amount);
                Log.d("SendActivity", "value selected:" + u.getValue());
                Log.d("SendActivity", "total value selected:" + totalValueSelected);
                Log.d("SendActivity", "nb inputs:" + u.getOutpoints().size());
                break;
            }
        }
        if (selectedUTXO.size() == 0) {
            // sort in descending order by value
            Collections.sort(_utxos, new UTXO.UTXOComparator());
            int selected = 0;
            int p2pkh = 0;
            int p2sh_p2wpkh = 0;
            int p2wpkh = 0;
            // get largest UTXOs > than spend + fee + dust
            for (UTXO u : _utxos) {
                selectedUTXO.add(u);
                totalValueSelected += u.getValue();
                selected += u.getOutpoints().size();
                // Log.d("SendActivity", "value selected:" + u.getValue());
                // Log.d("SendActivity", "total value selected/threshold:" + totalValueSelected + "/" + (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFee(selected, 2).longValue()));
                Triple<Integer, Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(new Vector<MyTransactionOutPoint>(u.getOutpoints()));
                p2pkh += outpointTypes.getLeft();
                p2sh_p2wpkh += outpointTypes.getMiddle();
                p2wpkh += outpointTypes.getRight();
                if (totalValueSelected >= (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFeeSegwit(p2pkh, p2sh_p2wpkh, p2wpkh, 2).longValue())) {
                    Log.d("SendActivity", "spend type:" + SPEND_TYPE);
                    Log.d("SendActivity", "multiple outputs");
                    Log.d("SendActivity", "amount:" + amount);
                    Log.d("SendActivity", "total value selected:" + totalValueSelected);
                    Log.d("SendActivity", "nb inputs:" + selected);
                    break;
                }
            }
        }
    } else if (pair != null) {
        selectedUTXO.clear();
        receivers.clear();
        long inputAmount = 0L;
        long outputAmount = 0L;
        for (MyTransactionOutPoint outpoint : pair.getLeft()) {
            UTXO u = new UTXO();
            List<MyTransactionOutPoint> outs = new ArrayList<MyTransactionOutPoint>();
            outs.add(outpoint);
            u.setOutpoints(outs);
            totalValueSelected += u.getValue();
            selectedUTXO.add(u);
            inputAmount += u.getValue();
        }
        for (TransactionOutput output : pair.getRight()) {
            try {
                Script script = new Script(output.getScriptBytes());
                if (Bech32Util.getInstance().isP2WPKHScript(Hex.toHexString(output.getScriptBytes()))) {
                    receivers.put(Bech32Util.getInstance().getAddressFromScript(script), BigInteger.valueOf(output.getValue().longValue()));
                } else {
                    receivers.put(script.getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString(), BigInteger.valueOf(output.getValue().longValue()));
                }
                outputAmount += output.getValue().longValue();
            } catch (Exception e) {
                Toast.makeText(SendActivity.this, R.string.error_bip126_output, Toast.LENGTH_SHORT).show();
                return false;
            }
        }
        fee = BigInteger.valueOf(inputAmount - outputAmount);
    } else {
        Toast.makeText(SendActivity.this, R.string.cannot_select_utxo, Toast.LENGTH_SHORT).show();
        return false;
    }
    // do spend here
    if (selectedUTXO.size() > 0) {
        // estimate fee for simple spend, already done if boltzmann
        if (SPEND_TYPE == SPEND_SIMPLE) {
            List<MyTransactionOutPoint> outpoints = new ArrayList<MyTransactionOutPoint>();
            for (UTXO utxo : selectedUTXO) {
                outpoints.addAll(utxo.getOutpoints());
            }
            Triple<Integer, Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(new Vector(outpoints));
            if (amount == balance) {
                fee = FeeUtil.getInstance().estimatedFeeSegwit(outpointTypes.getLeft(), outpointTypes.getMiddle(), outpointTypes.getRight(), 1);
                amount -= fee.longValue();
                receivers.clear();
                receivers.put(address, BigInteger.valueOf(amount));
                // 
                // fee sanity check
                // 
                restoreChangeIndexes();
                Transaction tx = SendFactory.getInstance(SendActivity.this).makeTransaction(account, outpoints, receivers);
                tx = SendFactory.getInstance(SendActivity.this).signTransaction(tx, account);
                byte[] serialized = tx.bitcoinSerialize();
                Log.d("SendActivity", "size:" + serialized.length);
                Log.d("SendActivity", "vsize:" + tx.getVirtualTransactionSize());
                Log.d("SendActivity", "fee:" + fee.longValue());
                if ((tx.hasWitness() && (fee.longValue() < tx.getVirtualTransactionSize())) || (!tx.hasWitness() && (fee.longValue() < serialized.length))) {
                    Toast.makeText(SendActivity.this, R.string.insufficient_fee, Toast.LENGTH_SHORT).show();
                    return false;
                }
            // 
            // 
            // 
            } else {
                fee = FeeUtil.getInstance().estimatedFeeSegwit(outpointTypes.getLeft(), outpointTypes.getMiddle(), outpointTypes.getRight(), 2);
            }
        }
        Log.d("SendActivity", "spend type:" + SPEND_TYPE);
        Log.d("SendActivity", "amount:" + amount);
        Log.d("SendActivity", "total value selected:" + totalValueSelected);
        Log.d("SendActivity", "fee:" + fee.longValue());
        Log.d("SendActivity", "nb inputs:" + selectedUTXO.size());
        change = totalValueSelected - (amount + fee.longValue());
        if (change > 0L && change < SamouraiWallet.bDust.longValue() && SPEND_TYPE == SPEND_SIMPLE) {
            AlertDialog.Builder dlg = new AlertDialog.Builder(SendActivity.this).setTitle(R.string.app_name).setMessage(R.string.change_is_dust).setCancelable(false).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {

                public void onClick(DialogInterface dialog, int whichButton) {
                    dialog.dismiss();
                }
            });
            if (!isFinishing()) {
                dlg.show();
            }
            return false;
        }
        _change = change;
        final BigInteger _fee = fee;
        String dest = null;
        if (strPCode != null && strPCode.length() > 0) {
            dest = BIP47Meta.getInstance().getDisplayLabel(strPCode);
        } else {
            dest = address;
        }
        strCannotDoBoltzmann = "";
        if (SendAddressUtil.getInstance().get(address) == 1) {
            strPrivacyWarning = getString(R.string.send_privacy_warning) + "\n\n";
        } else {
            strPrivacyWarning = "";
        }
        if (!canDoBoltzmann) {
            restoreChangeIndexes();
            sendTransactionDetailsView.getStoneWallSwitch().setOnClickListener(null);
            sendTransactionDetailsView.getStoneWallSwitch().setEnabled(false);
            sendTransactionDetailsView.enableStonewall(false);
            sendTransactionDetailsView.setEntropyBarStoneWallX1(null);
            sendTransactionDetailsView.getStoneWallSwitch().setOnCheckedChangeListener(onCheckedChangeListener);
            if (account == WhirlpoolMeta.getInstance(SendActivity.this).getWhirlpoolPostmix()) {
                strCannotDoBoltzmann = getString(R.string.boltzmann_cannot) + "\n\n";
            }
        }
        message = strCannotDoBoltzmann + strPrivacyWarning + "Send " + Coin.valueOf(amount).toPlainString() + " to " + dest + " (fee:" + Coin.valueOf(_fee.longValue()).toPlainString() + ")?\n";
        if (selectedCahootsType == SelectCahootsType.type.NONE) {
            tvTotalFee.setText(Coin.valueOf(_fee.longValue()).toPlainString().concat(" BTC"));
        } else {
            tvTotalFee.setText("__");
        }
        double value = Double.parseDouble(String.valueOf(_fee.add(BigInteger.valueOf(amount))));
        btnSend.setText("send ".concat(String.format(Locale.ENGLISH, "%.8f", getBtcValue(value))).concat(" BTC"));
        switch(selectedCahootsType) {
            case STONEWALLX2_MANUAL:
                {
                    sendTransactionDetailsView.showStonewallX2Layout("Manual", 1000);
                    btnSend.setBackgroundResource(R.drawable.button_blue);
                    btnSend.setText(getString(R.string.begin_stonewallx2));
                    break;
                }
            case STONEWALLX2_SAMOURAI:
                {
                    sendTransactionDetailsView.showStonewallX2Layout("Samourai Wallet", 1000);
                    break;
                }
            case STOWAWAY:
                {
                    // mixingPartner.setText("Samourai Wallet");
                    sendTransactionDetailsView.showStowawayLayout(address, null, 1000);
                    btnSend.setBackgroundResource(R.drawable.button_blue);
                    btnSend.setText(getString(R.string.begin_stowaway));
                    break;
                }
            case NONE:
                {
                    sendTransactionDetailsView.showStonewallx1Layout(null);
                    // for ricochet entropy will be 0 always
                    if (SPEND_TYPE == SPEND_RICOCHET) {
                        break;
                    }
                    if (receivers.size() <= 1) {
                        sendTransactionDetailsView.setEntropyBarStoneWallX1ZeroBits();
                        break;
                    }
                    if (receivers.size() > 8) {
                        sendTransactionDetailsView.setEntropyBarStoneWallX1(null);
                        break;
                    }
                    CalculateEntropy(selectedUTXO, receivers).subscribeOn(Schedulers.computation()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<TxProcessorResult>() {

                        @Override
                        public void onSubscribe(Disposable d) {
                        }

                        @Override
                        public void onNext(TxProcessorResult entropyResult) {
                            sendTransactionDetailsView.setEntropyBarStoneWallX1(entropyResult);
                        }

                        @Override
                        public void onError(Throwable e) {
                            sendTransactionDetailsView.setEntropyBarStoneWallX1(null);
                            e.printStackTrace();
                        }

                        @Override
                        public void onComplete() {
                        }
                    });
                    break;
                }
            default:
                {
                    btnSend.setBackgroundResource(R.drawable.button_green);
                    btnSend.setText("send ".concat(String.format(Locale.ENGLISH, "%.8f", getBtcValue((double) amount))).concat(" BTC"));
                }
        }
        return true;
    }
    return false;
}
Also used : AlertDialog(android.app.AlertDialog) TransactionOutput(org.bitcoinj.core.TransactionOutput) DialogInterface(android.content.DialogInterface) ArrayList(java.util.ArrayList) TxProcessorResult(com.samourai.boltzmann.processor.TxProcessorResult) Observer(io.reactivex.Observer) ArrayList(java.util.ArrayList) List(java.util.List) Vector(java.util.Vector) CompositeDisposable(io.reactivex.disposables.CompositeDisposable) Disposable(io.reactivex.disposables.Disposable) Script(org.bitcoinj.script.Script) JSONException(org.json.JSONException) JSONException(org.json.JSONException) IOException(java.io.IOException) ParseException(java.text.ParseException) UnsupportedEncodingException(java.io.UnsupportedEncodingException) MnemonicException(org.bitcoinj.crypto.MnemonicException) Triple(org.apache.commons.lang3.tuple.Triple) BigInteger(java.math.BigInteger) UTXOCoin(com.samourai.wallet.utxos.models.UTXOCoin) Transaction(org.bitcoinj.core.Transaction) BigInteger(java.math.BigInteger) ParseException(java.text.ParseException)

Aggregations

UTXOCoin (com.samourai.wallet.utxos.models.UTXOCoin)20 ArrayList (java.util.ArrayList)16 CompositeDisposable (io.reactivex.disposables.CompositeDisposable)7 Disposable (io.reactivex.disposables.Disposable)7 MyTransactionOutPoint (com.samourai.wallet.send.MyTransactionOutPoint)6 List (java.util.List)6 View (android.view.View)5 ImageView (android.widget.ImageView)5 TextView (android.widget.TextView)5 IOException (java.io.IOException)5 MnemonicException (org.bitcoinj.crypto.MnemonicException)5 AlertDialog (android.app.AlertDialog)4 Intent (android.content.Intent)4 Bundle (android.os.Bundle)4 AppCompatActivity (android.support.v7.app.AppCompatActivity)4 MenuItem (android.view.MenuItem)4 Toast (android.widget.Toast)4 R (com.samourai.wallet.R)4 UTXO (com.samourai.wallet.send.UTXO)4 AndroidSchedulers (io.reactivex.android.schedulers.AndroidSchedulers)4