¿Hay alguna manera de construir y enviar una transacción BitcoinJ
sin una billetera? Solo quiero construir mi transacción a partir de un utxo y transmitirla.
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::sendCoinsOffline
para completar y confirmar el TX, pero creo que podrías deshacerte de él si entiendes qué Wallet::completeTx
hacer 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 completeTx
y sendCoinsOffline
. De hecho, tuve que hacer esto hoy para mis propios fines al subclasificar la Wallet
clase. 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();
}
}
}