¿Cómo se crea una transacción desde un UTXO en BitcoinJ?

¿Hay alguna manera de construir y enviar una transacción BitcoinJsin una billetera? Solo quiero construir mi transacción a partir de un utxo y transmitirla.

Respuestas (1)

Algo así como. Aquí hay un código para empezar. Crea una transacción con una entrada y dos salidas. Una salida envía algunas monedas, otra envía algunos datos. Sin embargo, todavía se usa Wallet::sendCoinsOfflinepara completar y confirmar el TX, pero creo que podrías deshacerte de él si entiendes qué Wallet::completeTxhacer Wallet::commitTx.

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!
    }
}

Edición posterior: Así que aquí hay un ejemplo de cómo puede modificar completeTxy sendCoinsOffline. De hecho, tuve que hacer esto hoy para mis propios fines al subclasificar la Walletclase. Probablemente necesite tomar una ruta diferente a la subclasificación Wallet, pero esto debería darle una idea de lo que debe hacer.

Advertencia: este código modificado se encarga de firmar y pagar la tarifa solo para este tipo especial de transacciones con una entrada y dos salidas. Parece funcionar hasta donde lo he probado.

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();
        }
    }
}