2

Is there a way to construct and send a transaction in BitcoinJ without a wallet? I just want to build my transaction from a utxo and broadcast it.

1

Sort of. Here's some code to get you started. It creates a transaction with one input and two outputs. One output sends some coins, another one commits some data. However, it still uses Wallet::sendCoinsOffline to complete and commit the TX, but I think you could get rid of it if you understand what Wallet::completeTx and Wallet::commitTx do.

public class App extends WalletAppKit {
    public void commitStatement(String statement) throws InsufficientMoneyException {
        Address addr = getSomeAddress();
        TransactionOutput prevLink = getSomeUtxo();
        NetworkParameters params = RegTestParams.get(); // regtest mode

        byte[] data = statement.getBytes();
        if(data.length > 80) {
            throw new RuntimeException("OP_RETURN data cannot exceed 80 bytes");
        }

        Transaction tx = new Transaction(params);       

        log.trace("prevLink TX for '" + statement + "': " + prevLink.getParentTransaction());

        tx.addInput(prevLink);

        Coin feeAmt = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
        Coin opRetAmt = Transaction.MIN_NONDUST_OUTPUT;
        Coin changeAmt = prevLink.getValue().minus(opRetAmt).minus(feeAmt);

        // 1st output: send coins
        tx.addOutput(changeAmt, addr);
        // 2nd output: commit some data
        tx.addOutput(opRetAmt, ScriptBuilder.createOpReturnScript(data));

        log.trace("TX for '" + statement + "' before SendRequest: " + tx);
        SendRequest req = SendRequest.forTx(tx);
        // Want inputs and outputs to keep their order
        req.shuffleOutputs = false;
        req.ensureMinRequiredFee = true;

        log.trace("SendRequest for '" + statement + "' before completeTx: " + req);
        wallet().sendCoinsOffline(req);

        // NOTE: At this point, the TX is saved in the wallet!
    }
}

Later edit: So here's an example of how you can modify completeTx and sendCoinsOffline. I actually had to do this today for my own purposes by subclassing the Wallet class. You probably need to take a different route than subclassing Wallet but this should give you an idea of what you need to do.

Warning: This modified code takes care of signing and paying the fee only for these special-kind of transactions with one input and two outputs. Seems to work so far as I've tested it.

public class MyWallet extends Wallet {
    private boolean payFee(Transaction tx, Coin feePerKb, boolean ensureMinRequiredFee) {
        final int size = tx.unsafeBitcoinSerialize().length;
        Coin fee = feePerKb.multiply(size).divide(1000);

        if (ensureMinRequiredFee && fee.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
            fee = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;

        TransactionOutput output = tx.getOutput(0);
        output.setValue(output.getValue().subtract(fee));

        return !output.isDust();
    }

    public void myCompleteTx(SendRequest req) throws InsufficientMoneyException {
        lock.lock();
        try {
            // Print the output value
            Coin value = Coin.ZERO;
            for (TransactionOutput output : req.tx.getOutputs()) {
                value = value.add(output.getValue());
            }

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

            // Check for dusty sends and the OP_RETURN limit.
            if (req.ensureMinRequiredFee && !req.emptyWallet) { // Min fee checking is handled later for emptyWallet.
                int opReturnCount = 0;
                for (TransactionOutput output : req.tx.getOutputs()) {
                    if (output.isDust())
                        throw new DustySendRequested();
                    if (output.getScriptPubKey().isOpReturn())
                        ++opReturnCount;
                }
                if (opReturnCount > 1) // Only 1 OP_RETURN per transaction allowed.
                    throw new MultipleOpReturnRequested();
            }

            // Pay for the TX fee, depending on the TX size.
            Coin feePerKb = req.feePerKb == null ? Coin.ZERO : req.feePerKb;
            if (!payFee(req.tx, feePerKb, req.ensureMinRequiredFee))
                throw new CouldNotAdjustDownwards();

            // Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.
            if (req.signInputs)
                signTransaction(req);

            // Check size.
            final int size = req.tx.unsafeBitcoinSerialize().length;
            if (size > Transaction.MAX_STANDARD_TX_SIZE)
                throw new ExceededMaxTransactionSize();

            final Coin calculatedFee = req.tx.getFee();
            if (calculatedFee != null)
                log.debug("  with a fee of {}/kB, {} for {} bytes",
                        calculatedFee.multiply(1000).divide(size).toFriendlyString(), calculatedFee.toFriendlyString(),
                        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);
            // Record the exchange rate that was valid when the transaction was completed.
            req.tx.setExchangeRate(req.exchangeRate);
            req.tx.setMemo(req.memo);
            //req.completed = true; // FIXME: ALIN: This field is private, can't set it to true, but thankfully this is just for debugging.
            log.debug("  completed: {}", req.tx);
        } finally {
            lock.unlock();
        }
    }

    public Transaction mySendCoinsOffline(SendRequest request) throws InsufficientMoneyException {
        lock.lock();
        try {
            myCompleteTx(request);
            commitTx(request.tx);
            return request.tx;
        } finally {
            lock.unlock();
        }
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.