Search in sources :

Example 1 with TransactionOutput

use of com.sparrowwallet.drongo.protocol.TransactionOutput in project sparrow by sparrowwallet.

the class OutputsController method initialiseView.

private void initialiseView() {
    Transaction tx = outputsForm.getTransaction();
    count.setText(Integer.toString(tx.getOutputs().size()));
    long totalAmt = 0;
    for (TransactionOutput output : tx.getOutputs()) {
        totalAmt += output.getValue();
    }
    total.setValue(totalAmt);
    if (totalAmt > 0) {
        addPieData(outputsPie, tx.getOutputs());
    }
}
Also used : TransactionOutput(com.sparrowwallet.drongo.protocol.TransactionOutput) Transaction(com.sparrowwallet.drongo.protocol.Transaction)

Example 2 with TransactionOutput

use of com.sparrowwallet.drongo.protocol.TransactionOutput in project sparrow by sparrowwallet.

the class TransactionFormController method addPieData.

protected void addPieData(PieChart pie, List<TransactionOutput> outputs) {
    ObservableList<PieChart.Data> outputsPieData = FXCollections.observableArrayList();
    long totalAmt = 0;
    for (int i = 0; i < outputs.size(); i++) {
        TransactionOutput output = outputs.get(i);
        String name = "#" + i;
        try {
            Address[] addresses = output.getScript().getToAddresses();
            if (addresses.length == 1) {
                name = name + " " + addresses[0].getAddress();
            } else {
                name = name + " [" + addresses[0].getAddress() + ",...]";
            }
        } catch (NonStandardScriptException e) {
        // ignore
        }
        totalAmt += output.getValue();
        outputsPieData.add(new PieChart.Data(name, output.getValue()));
    }
    addPieData(pie, outputsPieData);
}
Also used : NonStandardScriptException(com.sparrowwallet.drongo.protocol.NonStandardScriptException) TransactionOutput(com.sparrowwallet.drongo.protocol.TransactionOutput) Address(com.sparrowwallet.drongo.address.Address) PieChart(javafx.scene.chart.PieChart) TransactionTabData(com.sparrowwallet.sparrow.TransactionTabData)

Example 3 with TransactionOutput

use of com.sparrowwallet.drongo.protocol.TransactionOutput in project sparrow by sparrowwallet.

the class SparrowDataSource method fetchWalletResponse.

@Override
protected WalletResponse fetchWalletResponse() throws Exception {
    WalletResponse walletResponse = new WalletResponse();
    walletResponse.wallet = new WalletResponse.Wallet();
    Map<Sha256Hash, BlockTransaction> allTransactions = new HashMap<>();
    Map<Sha256Hash, String> allTransactionsZpubs = new HashMap<>();
    List<WalletResponse.Address> addresses = new ArrayList<>();
    List<WalletResponse.Tx> txes = new ArrayList<>();
    List<UnspentOutput> unspentOutputs = new ArrayList<>();
    int storedBlockHeight = 0;
    String[] zpubs = getWalletSupplier().getPubs(true);
    for (String zpub : zpubs) {
        Wallet wallet = getWallet(zpub);
        if (wallet == null) {
            log.debug("No wallet for " + zpub + " found");
            continue;
        }
        Map<Sha256Hash, BlockTransaction> walletTransactions = wallet.getWalletTransactions();
        allTransactions.putAll(walletTransactions);
        walletTransactions.keySet().forEach(txid -> allTransactionsZpubs.put(txid, zpub));
        if (wallet.getStoredBlockHeight() != null) {
            storedBlockHeight = Math.max(storedBlockHeight, wallet.getStoredBlockHeight());
        }
        WalletResponse.Address address = new WalletResponse.Address();
        List<ExtendedKey.Header> headers = ExtendedKey.Header.getHeaders(Network.get());
        ExtendedKey.Header header = headers.stream().filter(head -> head.getDefaultScriptType().equals(wallet.getScriptType()) && !head.isPrivateKey()).findFirst().orElse(ExtendedKey.Header.xpub);
        address.address = wallet.getKeystores().get(0).getExtendedPublicKey().toString(header);
        int receiveIndex = wallet.getNode(KeyPurpose.RECEIVE).getHighestUsedIndex() == null ? 0 : wallet.getNode(KeyPurpose.RECEIVE).getHighestUsedIndex() + 1;
        address.account_index = wallet.getMixConfig() != null ? Math.max(receiveIndex, wallet.getMixConfig().getReceiveIndex()) : receiveIndex;
        int changeIndex = wallet.getNode(KeyPurpose.CHANGE).getHighestUsedIndex() == null ? 0 : wallet.getNode(KeyPurpose.CHANGE).getHighestUsedIndex() + 1;
        address.change_index = wallet.getMixConfig() != null ? Math.max(changeIndex, wallet.getMixConfig().getChangeIndex()) : changeIndex;
        address.n_tx = walletTransactions.size();
        addresses.add(address);
        for (Map.Entry<BlockTransactionHashIndex, WalletNode> utxo : wallet.getWalletUtxos().entrySet()) {
            BlockTransaction blockTransaction = wallet.getWalletTransaction(utxo.getKey().getHash());
            if (blockTransaction != null && utxo.getKey().getStatus() != Status.FROZEN) {
                unspentOutputs.add(Whirlpool.getUnspentOutput(utxo.getValue(), blockTransaction, (int) utxo.getKey().getIndex()));
            }
        }
    }
    for (BlockTransaction blockTransaction : allTransactions.values()) {
        WalletResponse.Tx tx = new WalletResponse.Tx();
        tx.block_height = blockTransaction.getHeight();
        tx.hash = blockTransaction.getHashAsString();
        tx.locktime = blockTransaction.getTransaction().getLocktime();
        tx.version = (int) blockTransaction.getTransaction().getVersion();
        tx.inputs = new WalletResponse.TxInput[blockTransaction.getTransaction().getInputs().size()];
        for (int i = 0; i < blockTransaction.getTransaction().getInputs().size(); i++) {
            TransactionInput txInput = blockTransaction.getTransaction().getInputs().get(i);
            tx.inputs[i] = new WalletResponse.TxInput();
            tx.inputs[i].vin = txInput.getIndex();
            tx.inputs[i].sequence = txInput.getSequenceNumber();
            if (allTransactionsZpubs.containsKey(txInput.getOutpoint().getHash())) {
                tx.inputs[i].prev_out = new WalletResponse.TxOut();
                tx.inputs[i].prev_out.txid = txInput.getOutpoint().getHash().toString();
                tx.inputs[i].prev_out.vout = (int) txInput.getOutpoint().getIndex();
                BlockTransaction spentTransaction = allTransactions.get(txInput.getOutpoint().getHash());
                if (spentTransaction != null) {
                    TransactionOutput spentOutput = spentTransaction.getTransaction().getOutputs().get((int) txInput.getOutpoint().getIndex());
                    tx.inputs[i].prev_out.value = spentOutput.getValue();
                }
                tx.inputs[i].prev_out.xpub = new UnspentOutput.Xpub();
                tx.inputs[i].prev_out.xpub.m = allTransactionsZpubs.get(txInput.getOutpoint().getHash());
            }
        }
        tx.out = new WalletResponse.TxOutput[blockTransaction.getTransaction().getOutputs().size()];
        for (int i = 0; i < blockTransaction.getTransaction().getOutputs().size(); i++) {
            TransactionOutput txOutput = blockTransaction.getTransaction().getOutputs().get(i);
            tx.out[i] = new WalletResponse.TxOutput();
            tx.out[i].n = txOutput.getIndex();
            tx.out[i].value = txOutput.getValue();
            tx.out[i].xpub = new UnspentOutput.Xpub();
            tx.out[i].xpub.m = allTransactionsZpubs.get(blockTransaction.getHash());
        }
        txes.add(tx);
    }
    walletResponse.addresses = addresses.toArray(new WalletResponse.Address[0]);
    walletResponse.txs = txes.toArray(new WalletResponse.Tx[0]);
    walletResponse.unspent_outputs = unspentOutputs.toArray(new UnspentOutput[0]);
    walletResponse.info = new WalletResponse.Info();
    walletResponse.info.latest_block = new WalletResponse.InfoBlock();
    walletResponse.info.latest_block.height = AppServices.getCurrentBlockHeight() == null ? storedBlockHeight : AppServices.getCurrentBlockHeight();
    walletResponse.info.latest_block.hash = Sha256Hash.ZERO_HASH.toString();
    walletResponse.info.latest_block.time = AppServices.getLatestBlockHeader() == null ? 1 : AppServices.getLatestBlockHeader().getTime();
    walletResponse.info.fees = new LinkedHashMap<>();
    for (MinerFeeTarget target : MinerFeeTarget.values()) {
        walletResponse.info.fees.put(target.getValue(), getMinerFeeSupplier().getFee(target));
    }
    return walletResponse;
}
Also used : TransactionOutput(com.sparrowwallet.drongo.protocol.TransactionOutput) WalletResponse(com.samourai.wallet.api.backend.beans.WalletResponse) Sha256Hash(com.sparrowwallet.drongo.protocol.Sha256Hash) UnspentOutput(com.samourai.wallet.api.backend.beans.UnspentOutput) TransactionInput(com.sparrowwallet.drongo.protocol.TransactionInput) MinerFeeTarget(com.samourai.wallet.api.backend.MinerFeeTarget) WhirlpoolWallet(com.samourai.whirlpool.client.wallet.WhirlpoolWallet) HD_Wallet(com.samourai.wallet.hd.HD_Wallet) ExtendedKey(com.sparrowwallet.drongo.ExtendedKey)

Example 4 with TransactionOutput

use of com.sparrowwallet.drongo.protocol.TransactionOutput in project sparrow by sparrowwallet.

the class Payjoin method checkProposal.

private void checkProposal(PSBT original, PSBT proposal, int changeOutputIndex, long maxAdditionalFeeContribution, boolean allowOutputSubstitution) throws PayjoinReceiverException {
    Queue<Map.Entry<TransactionInput, PSBTInput>> originalInputs = new ArrayDeque<>();
    for (int i = 0; i < original.getPsbtInputs().size(); i++) {
        originalInputs.add(Map.entry(original.getTransaction().getInputs().get(i), original.getPsbtInputs().get(i)));
    }
    Queue<Map.Entry<TransactionOutput, PSBTOutput>> originalOutputs = new ArrayDeque<>();
    for (int i = 0; i < original.getPsbtOutputs().size(); i++) {
        originalOutputs.add(Map.entry(original.getTransaction().getOutputs().get(i), original.getPsbtOutputs().get(i)));
    }
    // Checking that the PSBT of the receiver is clean
    if (!proposal.getExtendedPublicKeys().isEmpty()) {
        throw new PayjoinReceiverException("Global xpubs should not be included in the receiver's PSBT");
    }
    Transaction originalTx = original.getTransaction();
    Transaction proposalTx = proposal.getTransaction();
    // Verify that the transaction version, and nLockTime are unchanged.
    if (proposalTx.getVersion() != originalTx.getVersion()) {
        throw new PayjoinReceiverException("The proposal PSBT changed the transaction version");
    }
    if (proposalTx.getLocktime() != originalTx.getLocktime()) {
        throw new PayjoinReceiverException("The proposal PSBT changed the nLocktime");
    }
    Set<Long> sequences = new HashSet<>();
    // For each inputs in the proposal:
    for (PSBTInput proposedPSBTInput : proposal.getPsbtInputs()) {
        if (!proposedPSBTInput.getDerivedPublicKeys().isEmpty()) {
            throw new PayjoinReceiverException("The receiver added keypaths to an input");
        }
        if (!proposedPSBTInput.getPartialSignatures().isEmpty()) {
            throw new PayjoinReceiverException("The receiver added partial signatures to an input");
        }
        TransactionInput proposedTxIn = proposedPSBTInput.getInput();
        boolean isOriginalInput = originalInputs.size() > 0 && originalInputs.peek().getKey().getOutpoint().equals(proposedTxIn.getOutpoint());
        if (isOriginalInput) {
            Map.Entry<TransactionInput, PSBTInput> originalInput = originalInputs.remove();
            TransactionInput originalTxIn = originalInput.getKey();
            // Verify that sequence is unchanged.
            if (originalTxIn.getSequenceNumber() != proposedTxIn.getSequenceNumber()) {
                throw new PayjoinReceiverException("The proposed transaction input modified the sequence of one of the original inputs");
            }
            // Verify the PSBT input is not finalized
            if (proposedPSBTInput.isFinalized()) {
                throw new PayjoinReceiverException("The receiver finalized one of the original inputs");
            }
            // Verify that non_witness_utxo and witness_utxo are not specified.
            if (proposedPSBTInput.getNonWitnessUtxo() != null || proposedPSBTInput.getWitnessUtxo() != null) {
                throw new PayjoinReceiverException("The receiver added non_witness_utxo or witness_utxo to one of the original inputs");
            }
            sequences.add(proposedTxIn.getSequenceNumber());
            PSBTInput originalPSBTInput = originalInput.getValue();
            // Fill up the info from the original PSBT input so we can sign and get fees.
            proposedPSBTInput.setNonWitnessUtxo(originalPSBTInput.getNonWitnessUtxo());
            proposedPSBTInput.setWitnessUtxo(originalPSBTInput.getWitnessUtxo());
            // We fill up information we had on the signed PSBT, so we can sign it.
            proposedPSBTInput.getDerivedPublicKeys().putAll(originalPSBTInput.getDerivedPublicKeys());
            proposedPSBTInput.getProprietary().putAll(originalPSBTInput.getProprietary());
            proposedPSBTInput.setRedeemScript(originalPSBTInput.getFinalScriptSig().getFirstNestedScript());
            proposedPSBTInput.setWitnessScript(originalPSBTInput.getFinalScriptWitness().getWitnessScript());
            proposedPSBTInput.setSigHash(originalPSBTInput.getSigHash());
        } else {
            // Verify the PSBT input is finalized
            if (!proposedPSBTInput.isFinalized()) {
                throw new PayjoinReceiverException("The receiver did not finalize one of their inputs");
            }
            // Verify that non_witness_utxo or witness_utxo are filled in.
            if (proposedPSBTInput.getNonWitnessUtxo() == null && proposedPSBTInput.getWitnessUtxo() == null) {
                throw new PayjoinReceiverException("The receiver did not specify non_witness_utxo or witness_utxo for one of their inputs");
            }
            sequences.add(proposedTxIn.getSequenceNumber());
            // Verify that the payjoin proposal did not introduced mixed inputs' type.
            if (wallet.getScriptType() != proposedPSBTInput.getScriptType()) {
                throw new PayjoinReceiverException("Proposal script type of " + proposedPSBTInput.getScriptType() + " did not match wallet script type of " + wallet.getScriptType());
            }
        }
    }
    // Verify that all of sender's inputs from the original PSBT are in the proposal.
    if (!originalInputs.isEmpty()) {
        throw new PayjoinReceiverException("Some of the original inputs are not included in the proposal");
    }
    // Verify that the payjoin proposal did not introduced mixed inputs' sequence.
    if (sequences.size() != 1) {
        throw new PayjoinReceiverException("Mixed sequences detected in the proposal");
    }
    Long newFee = proposal.getFee();
    long additionalFee = newFee - original.getFee();
    if (additionalFee < 0) {
        throw new PayjoinReceiverException("The receiver decreased absolute fee");
    }
    TransactionOutput changeOutput = (changeOutputIndex > -1 ? originalTx.getOutputs().get(changeOutputIndex) : null);
    // For each outputs in the proposal:
    for (int i = 0; i < proposal.getPsbtOutputs().size(); i++) {
        PSBTOutput proposedPSBTOutput = proposal.getPsbtOutputs().get(i);
        // Verify that no keypaths is in the PSBT output
        if (!proposedPSBTOutput.getDerivedPublicKeys().isEmpty()) {
            throw new PayjoinReceiverException("The receiver added keypaths to an output");
        }
        TransactionOutput proposedTxOut = proposalTx.getOutputs().get(i);
        boolean isOriginalOutput = originalOutputs.size() > 0 && originalOutputs.peek().getKey().getScript().equals(proposedTxOut.getScript());
        if (isOriginalOutput) {
            Map.Entry<TransactionOutput, PSBTOutput> originalOutput = originalOutputs.remove();
            if (originalOutput.getKey() == changeOutput) {
                var actualContribution = changeOutput.getValue() - proposedTxOut.getValue();
                // The amount that was subtracted from the output's value is less than or equal to maxadditionalfeecontribution
                if (actualContribution > maxAdditionalFeeContribution) {
                    throw new PayjoinReceiverException("The actual contribution is more than maxadditionalfeecontribution");
                }
                // Make sure the actual contribution is only paying fee
                if (actualContribution > additionalFee) {
                    throw new PayjoinReceiverException("The actual contribution is not only paying fee");
                }
                // Make sure the actual contribution is only paying for fee incurred by additional inputs
                int additionalInputsCount = proposalTx.getInputs().size() - originalTx.getInputs().size();
                if (actualContribution > getSingleInputFee() * additionalInputsCount) {
                    throw new PayjoinReceiverException("The actual contribution is not only paying for additional inputs");
                }
            } else if (allowOutputSubstitution && originalOutput.getKey().getScript().equals(payjoinURI.getAddress().getOutputScript())) {
            // That's the payment output, the receiver may have changed it.
            } else {
                if (originalOutput.getKey().getValue() > proposedTxOut.getValue()) {
                    throw new PayjoinReceiverException("The receiver decreased the value of one of the outputs");
                }
            }
            PSBTOutput originalPSBTOutput = originalOutput.getValue();
            // We fill up information we had on the signed PSBT, so we can sign it.
            proposedPSBTOutput.getDerivedPublicKeys().putAll(originalPSBTOutput.getDerivedPublicKeys());
            proposedPSBTOutput.getProprietary().putAll(originalPSBTOutput.getProprietary());
            proposedPSBTOutput.setRedeemScript(originalPSBTOutput.getRedeemScript());
            proposedPSBTOutput.setWitnessScript(originalPSBTOutput.getWitnessScript());
        }
    }
    // Verify that all of sender's outputs from the original PSBT are in the proposal.
    if (!originalOutputs.isEmpty()) {
        // The payment output may have been substituted
        if (!allowOutputSubstitution || originalOutputs.size() != 1 || !originalOutputs.remove().getKey().getScript().equals(payjoinURI.getAddress().getOutputScript())) {
            throw new PayjoinReceiverException("Some of our outputs are not included in the proposal");
        }
    }
    // Add global pubkey map for signing
    proposal.getExtendedPublicKeys().putAll(psbt.getExtendedPublicKeys());
    proposal.getGlobalProprietary().putAll(psbt.getGlobalProprietary());
}
Also used : TransactionOutput(com.sparrowwallet.drongo.protocol.TransactionOutput) PSBTOutput(com.sparrowwallet.drongo.psbt.PSBTOutput) TransactionInput(com.sparrowwallet.drongo.protocol.TransactionInput) Transaction(com.sparrowwallet.drongo.protocol.Transaction) PSBTInput(com.sparrowwallet.drongo.psbt.PSBTInput) ImmutableMap(com.google.common.collect.ImmutableMap)

Example 5 with TransactionOutput

use of com.sparrowwallet.drongo.protocol.TransactionOutput in project sparrow by sparrowwallet.

the class SorobanController method getWalletTransaction.

private WalletTransaction getWalletTransaction(Wallet wallet, WalletTransaction walletTransaction, Transaction transaction, Map<Sha256Hash, BlockTransaction> inputTransactions) {
    Map<BlockTransactionHashIndex, WalletNode> allWalletUtxos = wallet.getWalletTxos();
    Map<BlockTransactionHashIndex, WalletNode> walletUtxos = new LinkedHashMap<>();
    Map<BlockTransactionHashIndex, WalletNode> externalUtxos = new LinkedHashMap<>();
    for (TransactionInput txInput : transaction.getInputs()) {
        Optional<BlockTransactionHashIndex> optWalletUtxo = allWalletUtxos.keySet().stream().filter(txo -> txo.getHash().equals(txInput.getOutpoint().getHash()) && txo.getIndex() == txInput.getOutpoint().getIndex()).findFirst();
        if (optWalletUtxo.isPresent()) {
            walletUtxos.put(optWalletUtxo.get(), allWalletUtxos.get(optWalletUtxo.get()));
        } else {
            BlockTransactionHashIndex externalUtxo;
            if (inputTransactions != null && inputTransactions.containsKey(txInput.getOutpoint().getHash())) {
                BlockTransaction blockTransaction = inputTransactions.get(txInput.getOutpoint().getHash());
                TransactionOutput txOutput = blockTransaction.getTransaction().getOutputs().get((int) txInput.getOutpoint().getIndex());
                externalUtxo = new BlockTransactionHashIndex(blockTransaction.getHash(), blockTransaction.getHeight(), blockTransaction.getDate(), blockTransaction.getFee(), txInput.getOutpoint().getIndex(), txOutput.getValue());
            } else {
                externalUtxo = new BlockTransactionHashIndex(txInput.getOutpoint().getHash(), 0, null, null, txInput.getOutpoint().getIndex(), 0);
            }
            externalUtxos.put(externalUtxo, null);
        }
    }
    List<Map<BlockTransactionHashIndex, WalletNode>> selectedUtxoSets = new ArrayList<>();
    selectedUtxoSets.add(walletUtxos);
    selectedUtxoSets.add(externalUtxos);
    Map<Address, WalletNode> walletAddresses = wallet.getWalletAddresses();
    List<Payment> payments = new ArrayList<>();
    Map<WalletNode, Long> changeMap = new LinkedHashMap<>();
    for (TransactionOutput txOutput : transaction.getOutputs()) {
        Address address = txOutput.getScript().getToAddress();
        if (address != null) {
            Optional<Payment> optPayment = walletTransaction == null ? Optional.empty() : walletTransaction.getPayments().stream().filter(payment -> payment.getAddress().equals(address) && payment.getAmount() == txOutput.getValue()).findFirst();
            if (optPayment.isPresent()) {
                payments.add(optPayment.get());
            } else if (walletAddresses.containsKey(address) && walletAddresses.get(address).getKeyPurpose() == KeyPurpose.CHANGE) {
                changeMap.put(walletAddresses.get(address), txOutput.getValue());
            } else {
                Payment payment = new Payment(address, null, txOutput.getValue(), false);
                if (transaction.getOutputs().stream().anyMatch(txo -> txo != txOutput && txo.getValue() == txOutput.getValue())) {
                    payment.setType(Payment.Type.MIX);
                }
                payments.add(payment);
            }
        }
    }
    long fee = calculateFee(walletTransaction, selectedUtxoSets, transaction);
    return new WalletTransaction(wallet, transaction, Collections.emptyList(), selectedUtxoSets, payments, changeMap, fee, inputTransactions);
}
Also used : Address(com.sparrowwallet.drongo.address.Address) Transaction(com.sparrowwallet.drongo.protocol.Transaction) java.util(java.util) Logger(org.slf4j.Logger) PSBT(com.sparrowwallet.drongo.psbt.PSBT) com.sparrowwallet.drongo.wallet(com.sparrowwallet.drongo.wallet) LoggerFactory(org.slf4j.LoggerFactory) Collectors(java.util.stream.Collectors) Cahoots(com.samourai.wallet.cahoots.Cahoots) KeyPurpose(com.sparrowwallet.drongo.KeyPurpose) ElectrumServer(com.sparrowwallet.sparrow.net.ElectrumServer) TransactionOutput(com.sparrowwallet.drongo.protocol.TransactionOutput) TransactionDiagram(com.sparrowwallet.sparrow.control.TransactionDiagram) Sha256Hash(com.sparrowwallet.drongo.protocol.Sha256Hash) PSBTParseException(com.sparrowwallet.drongo.psbt.PSBTParseException) Pattern(java.util.regex.Pattern) TransactionInput(com.sparrowwallet.drongo.protocol.TransactionInput) TransactionOutput(com.sparrowwallet.drongo.protocol.TransactionOutput) Address(com.sparrowwallet.drongo.address.Address) TransactionInput(com.sparrowwallet.drongo.protocol.TransactionInput)

Aggregations

TransactionOutput (com.sparrowwallet.drongo.protocol.TransactionOutput)7 Address (com.sparrowwallet.drongo.address.Address)3 Transaction (com.sparrowwallet.drongo.protocol.Transaction)3 TransactionInput (com.sparrowwallet.drongo.protocol.TransactionInput)3 NonStandardScriptException (com.sparrowwallet.drongo.protocol.NonStandardScriptException)2 Sha256Hash (com.sparrowwallet.drongo.protocol.Sha256Hash)2 ImmutableMap (com.google.common.collect.ImmutableMap)1 MinerFeeTarget (com.samourai.wallet.api.backend.MinerFeeTarget)1 UnspentOutput (com.samourai.wallet.api.backend.beans.UnspentOutput)1 WalletResponse (com.samourai.wallet.api.backend.beans.WalletResponse)1 Cahoots (com.samourai.wallet.cahoots.Cahoots)1 HD_Wallet (com.samourai.wallet.hd.HD_Wallet)1 WhirlpoolWallet (com.samourai.whirlpool.client.wallet.WhirlpoolWallet)1 ExtendedKey (com.sparrowwallet.drongo.ExtendedKey)1 KeyPurpose (com.sparrowwallet.drongo.KeyPurpose)1 PSBT (com.sparrowwallet.drongo.psbt.PSBT)1 PSBTInput (com.sparrowwallet.drongo.psbt.PSBTInput)1 PSBTOutput (com.sparrowwallet.drongo.psbt.PSBTOutput)1 PSBTParseException (com.sparrowwallet.drongo.psbt.PSBTParseException)1 com.sparrowwallet.drongo.wallet (com.sparrowwallet.drongo.wallet)1