class Wallet implements Serializable, BlockChainListener, PeerFilterProvider {

    public void completeTx(SendRequest req) throws InsufficientMoneyException {
        lock.lock();
        try {
            checkArgument(!req.completed, "Given SendRequest has already been completed.");
            // Calculate the amount of value we need to import.
            BigInteger value = BigInteger.ZERO;
            for (TransactionOutput output : req.tx.getOutputs()) {
                value = value.add(output.getValue());
            }
            BigInteger totalOutput = value;

            log.info("Completing send tx with {} outputs totalling {} satoshis (not including fees)",
                    req.tx.getOutputs().size(), value);

            // If any inputs have already been added, we don't need to get their value from wallet
            BigInteger totalInput = BigInteger.ZERO;
            for (TransactionInput input : req.tx.getInputs())
                if (input.getConnectedOutput() != null)
                    totalInput = totalInput.add(input.getConnectedOutput().getValue());
                else
                    log.warn("SendRequest transaction already has inputs but we don't know how much they are worth - they will be added to fee.");
            value = value.subtract(totalInput);

            List<TransactionInput> originalInputs = new ArrayList<TransactionInput>(req.tx.getInputs());

            // We need to know if we need to add an additional fee because one of our values are smaller than 0.01 BTC
            boolean needAtLeastReferenceFee = false;
            if (req.ensureMinRequiredFee && !req.emptyWallet) { // min fee checking is handled later for emptyWallet
                for (TransactionOutput output : req.tx.getOutputs())
                    if (output.getValue().compareTo(Utils.CENT) < 0) {
                        if (output.getValue().compareTo(output.getMinNonDustValue()) < 0)
                            throw new IllegalArgumentException("Tried to send dust with ensureMinRequiredFee set - no way to complete this");
                        needAtLeastReferenceFee = true;
                        break;
                    }
            }

            // Calculate a list of ALL potential candidates for spending and then ask a coin selector to provide us
            // with the actual outputs that'll be used to gather the required amount of value. In this way, users
            // can customize coin selection policies.
            //
            // Note that this code is poorly optimized: the spend candidates only alter when transactions in the wallet
            // change - it could be pre-calculated and held in RAM, and this is probably an optimization worth doing.
            // Note that output.isMine(this) needs to test the keychain which is currently an array, so it's
            // O(candidate outputs ^ keychain.size())! There's lots of low hanging fruit here.
            LinkedList<TransactionOutput> candidates = calculateAllSpendCandidates(true);
            CoinSelection bestCoinSelection;
            TransactionOutput bestChangeOutput = null;
            if (!req.emptyWallet) {
                // This can throw InsufficientMoneyException.
                FeeCalculation feeCalculation;
                feeCalculation = new FeeCalculation(req, value, originalInputs, needAtLeastReferenceFee, candidates);
                bestCoinSelection = feeCalculation.bestCoinSelection;
                bestChangeOutput = feeCalculation.bestChangeOutput;
            } else {
                // We're being asked to empty the wallet. What this means is ensuring "tx" has only a single output
                // of the total value we can currently spend as determined by the selector, and then subtracting the fee.
                checkState(req.tx.getOutputs().size() == 1, "Empty wallet TX must have a single output only.");
                CoinSelector selector = req.coinSelector == null ? coinSelector : req.coinSelector;
                bestCoinSelection = selector.select(NetworkParameters.MAX_MONEY, candidates);
                req.tx.getOutput(0).setValue(bestCoinSelection.valueGathered);
                totalOutput = bestCoinSelection.valueGathered;
            }

            for (TransactionOutput output : bestCoinSelection.gathered)
                req.tx.addInput(output);

            if (req.ensureMinRequiredFee && req.emptyWallet) {
                final BigInteger baseFee = req.fee == null ? BigInteger.ZERO : req.fee;
                final BigInteger feePerKb = req.feePerKb == null ? BigInteger.ZERO : req.feePerKb;
                Transaction tx = req.tx;
                if (!adjustOutputDownwardsForFee(tx, bestCoinSelection, baseFee, feePerKb))
                    throw new InsufficientMoneyException.CouldNotAdjustDownwards();
            }

            totalInput = totalInput.add(bestCoinSelection.valueGathered);

            if (bestChangeOutput != null) {
                req.tx.addOutput(bestChangeOutput);
                totalOutput = totalOutput.add(bestChangeOutput.getValue());
                log.info("  with {} coins change", bitcoinValueToFriendlyString(bestChangeOutput.getValue()));
            }
            final BigInteger calculatedFee = totalInput.subtract(totalOutput);
            if (calculatedFee.signum() > 0) {
                log.info("  with a fee of {}", bitcoinValueToFriendlyString(calculatedFee));
            }

            // Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.
            req.tx.signInputs(Transaction.SigHash.ALL, this, req.aesKey);

            // Check size.
            int size = req.tx.bitcoinSerialize().length;
            if (size > Transaction.MAX_STANDARD_TX_SIZE) {
                throw new IllegalArgumentException(
                        String.format("Transaction could not be created without exceeding max size: %d vs %d", size,
                            Transaction.MAX_STANDARD_TX_SIZE));
            }

            // Label the transaction as being self created. We can use this later to spend its change output even before
            // the transaction is confirmed. We deliberately won't bother notifying listeners here as there's not much
            // point - the user isn't interested in a confidence transition they made themselves.
            req.tx.getConfidence().setSource(TransactionConfidence.Source.SELF);
            // Label the transaction as being a user requested payment. This can be used to render GUI wallet
            // transaction lists more appropriately, especially when the wallet starts to generate transactions itself
            // for internal purposes.
            req.tx.setPurpose(Transaction.Purpose.USER_PAYMENT);
            req.completed = true;
            req.fee = calculatedFee;
            log.info("  completed: {}", req.tx);
        } finally {
            lock.unlock();
        }
    }

    public  Wallet(NetworkParameters params);
    public  Wallet(NetworkParameters params, KeyCrypter keyCrypter);

}