mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-20 18:21:57 +01:00
343 lines
10 KiB
JavaScript
343 lines
10 KiB
JavaScript
/**
|
|
* Cashier-BTC
|
|
* -----------
|
|
* Self-hosted bitcoin payment gateway
|
|
*
|
|
* https://github.com/Overtorment/Cashier-BTC
|
|
*
|
|
**/
|
|
const bitcoinjs = require('bitcoinjs-lib');
|
|
const _p2wpkh = bitcoinjs.payments.p2wpkh;
|
|
const _p2sh = bitcoinjs.payments.p2sh;
|
|
const toSatoshi = num => parseInt((num * 100000000).toFixed(0));
|
|
|
|
exports.createHDTransaction = function(utxos, toAddress, amount, fixedFee, changeAddress) {
|
|
let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0));
|
|
let amountToOutputSatoshi = parseInt(((amount - fixedFee) * 100000000).toFixed(0)); // how much payee should get
|
|
let txb = new bitcoinjs.TransactionBuilder();
|
|
txb.setVersion(1);
|
|
let unspentAmountSatoshi = 0;
|
|
let ourOutputs = {};
|
|
let outputNum = 0;
|
|
for (const unspent of utxos) {
|
|
if (unspent.confirmations < 1) {
|
|
// using only confirmed outputs
|
|
continue;
|
|
}
|
|
txb.addInput(unspent.txid, unspent.vout);
|
|
ourOutputs[outputNum] = ourOutputs[outputNum] || {};
|
|
ourOutputs[outputNum].keyPair = bitcoinjs.ECPair.fromWIF(unspent.wif);
|
|
unspentAmountSatoshi += unspent.amount;
|
|
if (unspentAmountSatoshi >= amountToOutputSatoshi + feeInSatoshis) {
|
|
// found enough inputs to satisfy payee and pay fees
|
|
break;
|
|
}
|
|
outputNum++;
|
|
}
|
|
if (unspentAmountSatoshi < amountToOutputSatoshi + feeInSatoshis) {
|
|
throw new Error('Not enough balance. Please, try sending a smaller amount.');
|
|
}
|
|
|
|
// adding outputs
|
|
|
|
txb.addOutput(toAddress, amountToOutputSatoshi);
|
|
if (amountToOutputSatoshi + feeInSatoshis < unspentAmountSatoshi) {
|
|
// sending less than we have, so the rest should go back
|
|
if (unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis > 3 * feeInSatoshis) {
|
|
// to prevent @dust error change transferred amount should be at least 3xfee.
|
|
// if not - we just dont send change and it wil add to fee
|
|
txb.addOutput(changeAddress, unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis);
|
|
}
|
|
}
|
|
|
|
// now, signing every input with a corresponding key
|
|
|
|
for (let c = 0; c <= outputNum; c++) {
|
|
txb.sign({
|
|
prevOutScriptType: 'p2pkh',
|
|
vin: c,
|
|
keyPair: ourOutputs[c].keyPair,
|
|
});
|
|
}
|
|
|
|
let tx = txb.build();
|
|
return tx.toHex();
|
|
};
|
|
|
|
exports.createHDSegwitTransaction = function(utxos, toAddress, amount, fixedFee, changeAddress) {
|
|
let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0));
|
|
let amountToOutputSatoshi = parseInt(((amount - fixedFee) * 100000000).toFixed(0)); // how much payee should get
|
|
let psbt = new bitcoinjs.Psbt();
|
|
psbt.setVersion(1);
|
|
let unspentAmountSatoshi = 0;
|
|
let ourOutputs = [];
|
|
let outputNum = 0;
|
|
for (const unspent of utxos) {
|
|
if (unspent.confirmations < 1) {
|
|
// using only confirmed outputs
|
|
continue;
|
|
}
|
|
let keyPair = bitcoinjs.ECPair.fromWIF(unspent.wif);
|
|
let p2wpkh = _p2wpkh({
|
|
pubkey: keyPair.publicKey,
|
|
});
|
|
let p2sh = _p2sh({
|
|
redeem: p2wpkh,
|
|
});
|
|
psbt.addInput({
|
|
hash: unspent.txid,
|
|
index: unspent.vout,
|
|
witnessUtxo: {
|
|
script: p2sh.output,
|
|
value: unspent.amount,
|
|
},
|
|
redeemScript: p2wpkh.output,
|
|
});
|
|
ourOutputs[outputNum] = ourOutputs[outputNum] || {};
|
|
ourOutputs[outputNum].keyPair = keyPair;
|
|
ourOutputs[outputNum].redeemScript = p2wpkh.output;
|
|
ourOutputs[outputNum].amount = unspent.amount;
|
|
unspentAmountSatoshi += unspent.amount;
|
|
if (unspentAmountSatoshi >= amountToOutputSatoshi + feeInSatoshis) {
|
|
// found enough inputs to satisfy payee and pay fees
|
|
break;
|
|
}
|
|
outputNum++;
|
|
}
|
|
|
|
if (unspentAmountSatoshi < amountToOutputSatoshi + feeInSatoshis) {
|
|
throw new Error('Not enough balance. Please, try sending a smaller amount.');
|
|
}
|
|
|
|
// adding outputs
|
|
|
|
psbt.addOutput({
|
|
address: toAddress,
|
|
value: amountToOutputSatoshi,
|
|
});
|
|
if (amountToOutputSatoshi + feeInSatoshis < unspentAmountSatoshi) {
|
|
// sending less than we have, so the rest should go back
|
|
if (unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis > 3 * feeInSatoshis) {
|
|
// to prevent @dust error change transferred amount should be at least 3xfee.
|
|
// if not - we just dont send change and it wil add to fee
|
|
psbt.addOutput({
|
|
address: changeAddress,
|
|
value: unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis,
|
|
});
|
|
}
|
|
}
|
|
|
|
// now, signing every input with a corresponding key
|
|
|
|
for (let c = 0; c <= outputNum; c++) {
|
|
psbt.signInput(c, ourOutputs[c].keyPair);
|
|
}
|
|
|
|
let tx = psbt.finalizeAllInputs().extractTransaction();
|
|
return tx.toHex();
|
|
};
|
|
|
|
exports.createSegwitTransaction = function(utxos, toAddress, amount, fixedFee, WIF, changeAddress, sequence) {
|
|
changeAddress = changeAddress || exports.WIF2segwitAddress(WIF);
|
|
if (sequence === undefined) {
|
|
sequence = bitcoinjs.Transaction.DEFAULT_SEQUENCE;
|
|
}
|
|
|
|
let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0));
|
|
let keyPair = bitcoinjs.ECPair.fromWIF(WIF);
|
|
let p2wpkh = _p2wpkh({
|
|
pubkey: keyPair.publicKey,
|
|
});
|
|
let p2sh = _p2sh({
|
|
redeem: p2wpkh,
|
|
});
|
|
|
|
let psbt = new bitcoinjs.Psbt();
|
|
psbt.setVersion(1);
|
|
let unspentAmount = 0;
|
|
for (const unspent of utxos) {
|
|
if (unspent.confirmations < 2) {
|
|
// using only confirmed outputs
|
|
continue;
|
|
}
|
|
const satoshis = parseInt((unspent.amount * 100000000).toFixed(0));
|
|
psbt.addInput({
|
|
hash: unspent.txid,
|
|
index: unspent.vout,
|
|
sequence,
|
|
witnessUtxo: {
|
|
script: p2sh.output,
|
|
value: satoshis,
|
|
},
|
|
redeemScript: p2wpkh.output,
|
|
});
|
|
unspentAmount += satoshis;
|
|
}
|
|
let amountToOutput = parseInt(((amount - fixedFee) * 100000000).toFixed(0));
|
|
psbt.addOutput({
|
|
address: toAddress,
|
|
value: amountToOutput,
|
|
});
|
|
if (amountToOutput + feeInSatoshis < unspentAmount) {
|
|
// sending less than we have, so the rest should go back
|
|
|
|
if (unspentAmount - amountToOutput - feeInSatoshis > 3 * feeInSatoshis) {
|
|
// to prevent @dust error change transferred amount should be at least 3xfee.
|
|
// if not - we just dont send change and it wil add to fee
|
|
psbt.addOutput({
|
|
address: changeAddress,
|
|
value: unspentAmount - amountToOutput - feeInSatoshis,
|
|
});
|
|
}
|
|
}
|
|
|
|
for (let c = 0; c < utxos.length; c++) {
|
|
psbt.signInput(c, keyPair);
|
|
}
|
|
|
|
let tx = psbt.finalizeAllInputs().extractTransaction();
|
|
return tx.toHex();
|
|
};
|
|
|
|
exports.createRBFSegwitTransaction = function(txhex, addressReplaceMap, feeDelta, WIF, utxodata) {
|
|
if (feeDelta < 0) {
|
|
throw Error('replace-by-fee requires increased fee, not decreased');
|
|
}
|
|
|
|
let tx = bitcoinjs.Transaction.fromHex(txhex);
|
|
|
|
// looking for latest sequence number in inputs
|
|
let highestSequence = 0;
|
|
for (let i of tx.ins) {
|
|
if (i.sequence > highestSequence) {
|
|
highestSequence = i.sequence;
|
|
}
|
|
}
|
|
let keyPair = bitcoinjs.ECPair.fromWIF(WIF);
|
|
let p2wpkh = _p2wpkh({
|
|
pubkey: keyPair.publicKey,
|
|
});
|
|
let p2sh = _p2sh({
|
|
redeem: p2wpkh,
|
|
});
|
|
|
|
// creating TX
|
|
let psbt = new bitcoinjs.Psbt();
|
|
psbt.setVersion(1);
|
|
for (let unspent of tx.ins) {
|
|
let txid = Buffer.from(unspent.hash)
|
|
.reverse()
|
|
.toString('hex');
|
|
let index = unspent.index;
|
|
let amount = utxodata[txid][index];
|
|
psbt.addInput({
|
|
hash: txid,
|
|
index,
|
|
sequence: highestSequence + 1,
|
|
witnessUtxo: {
|
|
script: p2sh.output,
|
|
value: amount,
|
|
},
|
|
redeemScript: p2wpkh.output,
|
|
});
|
|
}
|
|
|
|
for (let o of tx.outs) {
|
|
let outAddress = bitcoinjs.address.fromOutputScript(o.script);
|
|
if (addressReplaceMap[outAddress]) {
|
|
// means this is DESTINATION address, not messing with it's amount
|
|
// but replacing the address itseld
|
|
psbt.addOutput({
|
|
address: addressReplaceMap[outAddress],
|
|
value: o.value,
|
|
});
|
|
} else {
|
|
// CHANGE address, so we deduct increased fee from here
|
|
let feeDeltaInSatoshi = parseInt((feeDelta * 100000000).toFixed(0));
|
|
psbt.addOutput({
|
|
address: outAddress,
|
|
value: o.value - feeDeltaInSatoshi,
|
|
});
|
|
}
|
|
}
|
|
|
|
// signing
|
|
for (let c = 0; c < tx.ins.length; c++) {
|
|
psbt.signInput(c, keyPair);
|
|
}
|
|
|
|
let newTx = psbt.finalizeAllInputs().extractTransaction();
|
|
return newTx.toHex();
|
|
};
|
|
|
|
exports.generateNewSegwitAddress = function() {
|
|
let keyPair = bitcoinjs.ECPair.makeRandom();
|
|
let address = bitcoinjs.payments.p2sh({
|
|
redeem: bitcoinjs.payments.p2wpkh({
|
|
pubkey: keyPair.publicKey,
|
|
}),
|
|
}).address;
|
|
|
|
return {
|
|
address: address,
|
|
WIF: keyPair.toWIF(),
|
|
};
|
|
};
|
|
|
|
exports.URI = function(paymentInfo) {
|
|
let uri = 'bitcoin:';
|
|
uri += paymentInfo.address;
|
|
uri += '?amount=';
|
|
uri += parseFloat(paymentInfo.amount / 100000000);
|
|
uri += '&message=';
|
|
uri += encodeURIComponent(paymentInfo.message);
|
|
if (paymentInfo.label) {
|
|
uri += '&label=';
|
|
uri += encodeURIComponent(paymentInfo.label);
|
|
}
|
|
|
|
return uri;
|
|
};
|
|
|
|
exports.WIF2segwitAddress = function(WIF) {
|
|
let keyPair = bitcoinjs.ECPair.fromWIF(WIF);
|
|
return bitcoinjs.payments.p2sh({
|
|
redeem: bitcoinjs.payments.p2wpkh({
|
|
pubkey: keyPair.publicKey,
|
|
}),
|
|
}).address;
|
|
};
|
|
|
|
exports.createTransaction = function(utxos, toAddress, _amount, _fixedFee, WIF, fromAddress) {
|
|
let fixedFee = toSatoshi(_fixedFee);
|
|
let amountToOutput = toSatoshi(_amount - _fixedFee);
|
|
let pk = bitcoinjs.ECPair.fromWIF(WIF); // eslint-disable-line new-cap
|
|
let txb = new bitcoinjs.TransactionBuilder();
|
|
txb.setVersion(1);
|
|
let unspentAmount = 0;
|
|
for (const unspent of utxos) {
|
|
if (unspent.confirmations < 2) {
|
|
// using only confirmed outputs
|
|
continue;
|
|
}
|
|
txb.addInput(unspent.txid, unspent.vout);
|
|
unspentAmount += toSatoshi(unspent.amount);
|
|
}
|
|
txb.addOutput(toAddress, amountToOutput);
|
|
|
|
if (amountToOutput + fixedFee < unspentAmount) {
|
|
// sending less than we have, so the rest should go back
|
|
txb.addOutput(fromAddress, unspentAmount - amountToOutput - fixedFee);
|
|
}
|
|
|
|
for (let c = 0; c < utxos.length; c++) {
|
|
txb.sign({
|
|
prevOutScriptType: 'p2pkh',
|
|
vin: c,
|
|
keyPair: pk,
|
|
});
|
|
}
|
|
|
|
return txb.build().toHex();
|
|
};
|