Overtorment
5 years ago
32 changed files with 965 additions and 1487 deletions
@ -1,59 +0,0 @@ |
|||
import { LegacyWallet } from './legacy-wallet'; |
|||
const bitcoin = require('bitcoinjs-lib'); |
|||
|
|||
export class SegwitBech32Wallet extends LegacyWallet { |
|||
static type = 'segwitBech32'; |
|||
static typeReadable = 'P2 WPKH'; |
|||
|
|||
getAddress() { |
|||
if (this._address) return this._address; |
|||
let address; |
|||
try { |
|||
let keyPair = bitcoin.ECPair.fromWIF(this.secret); |
|||
if (!keyPair.compressed) { |
|||
console.warn('only compressed public keys are good for segwit'); |
|||
return false; |
|||
} |
|||
address = bitcoin.payments.p2wpkh({ |
|||
pubkey: keyPair.publicKey, |
|||
}).address; |
|||
} catch (err) { |
|||
return false; |
|||
} |
|||
this._address = address; |
|||
|
|||
return this._address; |
|||
} |
|||
|
|||
static witnessToAddress(witness) { |
|||
const pubKey = Buffer.from(witness, 'hex'); |
|||
return bitcoin.payments.p2wpkh({ |
|||
pubkey: pubKey, |
|||
network: bitcoin.networks.bitcoin, |
|||
}).address; |
|||
} |
|||
|
|||
/** |
|||
* Converts script pub key to bech32 address if it can. Returns FALSE if it cant. |
|||
* |
|||
* @param scriptPubKey |
|||
* @returns {boolean|string} Either bech32 address or false |
|||
*/ |
|||
static scriptPubKeyToAddress(scriptPubKey) { |
|||
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex'); |
|||
let ret; |
|||
try { |
|||
ret = bitcoin.payments.p2wpkh({ |
|||
output: scriptPubKey2, |
|||
network: bitcoin.networks.bitcoin, |
|||
}).address; |
|||
} catch (_) { |
|||
return false; |
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
allowSend() { |
|||
return false; |
|||
} |
|||
} |
@ -0,0 +1,149 @@ |
|||
import { LegacyWallet } from './legacy-wallet'; |
|||
const bitcoin = require('bitcoinjs-lib'); |
|||
const coinSelectAccumulative = require('coinselect/accumulative'); |
|||
const coinSelectSplit = require('coinselect/split'); |
|||
|
|||
export class SegwitBech32Wallet extends LegacyWallet { |
|||
static type = 'segwitBech32'; |
|||
static typeReadable = 'P2 WPKH'; |
|||
|
|||
getAddress() { |
|||
if (this._address) return this._address; |
|||
let address; |
|||
try { |
|||
let keyPair = bitcoin.ECPair.fromWIF(this.secret); |
|||
if (!keyPair.compressed) { |
|||
console.warn('only compressed public keys are good for segwit'); |
|||
return false; |
|||
} |
|||
address = bitcoin.payments.p2wpkh({ |
|||
pubkey: keyPair.publicKey, |
|||
}).address; |
|||
} catch (err) { |
|||
return false; |
|||
} |
|||
this._address = address; |
|||
|
|||
return this._address; |
|||
} |
|||
|
|||
static witnessToAddress(witness) { |
|||
const pubKey = Buffer.from(witness, 'hex'); |
|||
return bitcoin.payments.p2wpkh({ |
|||
pubkey: pubKey, |
|||
network: bitcoin.networks.bitcoin, |
|||
}).address; |
|||
} |
|||
|
|||
/** |
|||
* Converts script pub key to bech32 address if it can. Returns FALSE if it cant. |
|||
* |
|||
* @param scriptPubKey |
|||
* @returns {boolean|string} Either bech32 address or false |
|||
*/ |
|||
static scriptPubKeyToAddress(scriptPubKey) { |
|||
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex'); |
|||
let ret; |
|||
try { |
|||
ret = bitcoin.payments.p2wpkh({ |
|||
output: scriptPubKey2, |
|||
network: bitcoin.networks.bitcoin, |
|||
}).address; |
|||
} catch (_) { |
|||
return false; |
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @param utxos {Array.<{vout: Number, value: Number, txId: String, address: String, txhex: String, }>} List of spendable utxos |
|||
* @param targets {Array.<{value: Number, address: String}>} Where coins are going. If theres only 1 target and that target has no value - this will send MAX to that address (respecting fee rate) |
|||
* @param feeRate {Number} satoshi per byte |
|||
* @param changeAddress {String} Excessive coins will go back to that address |
|||
* @param sequence {Number} Used in RBF |
|||
* @param skipSigning {boolean} Whether we should skip signing, use returned `psbt` in that case |
|||
* @param masterFingerprint {number} Decimal number of wallet's master fingerprint |
|||
* @returns {{outputs: Array, tx: Transaction, inputs: Array, fee: Number, psbt: Psbt}} |
|||
*/ |
|||
createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false, masterFingerprint) { |
|||
if (!changeAddress) throw new Error('No change address provided'); |
|||
sequence = sequence || 0xffffffff; // disable RBF by default
|
|||
|
|||
let algo = coinSelectAccumulative; |
|||
if (targets.length === 1 && targets[0] && !targets[0].value) { |
|||
// we want to send MAX
|
|||
algo = coinSelectSplit; |
|||
} |
|||
|
|||
let { inputs, outputs, fee } = algo(utxos, targets, feeRate); |
|||
|
|||
// .inputs and .outputs will be undefined if no solution was found
|
|||
if (!inputs || !outputs) { |
|||
throw new Error('Not enough balance. Try sending smaller amount'); |
|||
} |
|||
|
|||
let psbt = new bitcoin.Psbt(); |
|||
|
|||
let c = 0; |
|||
let values = {}; |
|||
let keyPair; |
|||
|
|||
inputs.forEach(input => { |
|||
if (!skipSigning) { |
|||
// skiping signing related stuff
|
|||
keyPair = bitcoin.ECPair.fromWIF(this.secret); // secret is WIF
|
|||
} |
|||
values[c] = input.value; |
|||
c++; |
|||
|
|||
const pubkey = keyPair.publicKey; |
|||
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey }); |
|||
|
|||
psbt.addInput({ |
|||
hash: input.txid, |
|||
index: input.vout, |
|||
sequence, |
|||
witnessUtxo: { |
|||
script: p2wpkh.output, |
|||
value: input.value, |
|||
}, |
|||
}); |
|||
}); |
|||
|
|||
outputs.forEach(output => { |
|||
// if output has no address - this is change output
|
|||
if (!output.address) { |
|||
output.address = changeAddress; |
|||
} |
|||
|
|||
let outputData = { |
|||
address: output.address, |
|||
value: output.value, |
|||
}; |
|||
|
|||
psbt.addOutput(outputData); |
|||
}); |
|||
|
|||
if (!skipSigning) { |
|||
// skiping signing related stuff
|
|||
for (let cc = 0; cc < c; cc++) { |
|||
psbt.signInput(cc, keyPair); |
|||
} |
|||
} |
|||
|
|||
let tx; |
|||
if (!skipSigning) { |
|||
tx = psbt.finalizeAllInputs().extractTransaction(); |
|||
} |
|||
return { tx, inputs, outputs, fee, psbt }; |
|||
} |
|||
|
|||
allowSend() { |
|||
return true; |
|||
} |
|||
|
|||
allowSendMax() { |
|||
return true; |
|||
} |
|||
} |
@ -1,342 +0,0 @@ |
|||
/** |
|||
* 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(); |
|||
}; |
@ -0,0 +1,27 @@ |
|||
/* global it */ |
|||
import { HDLegacyBreadwalletWallet } from '../../class'; |
|||
const assert = require('assert'); |
|||
|
|||
it('Legacy HD Breadwallet works', async () => { |
|||
let hdBread = new HDLegacyBreadwalletWallet(); |
|||
hdBread.setSecret(process.env.HD_MNEMONIC_BREAD); |
|||
|
|||
assert.strictEqual(hdBread.validateMnemonic(), true); |
|||
assert.strictEqual(hdBread._getExternalAddressByIndex(0), '1ARGkNMdsBE36fJhddSwf8PqBXG3s4d2KU'); |
|||
assert.strictEqual(hdBread._getInternalAddressByIndex(0), '1JLvA5D7RpWgChb4A5sFcLNrfxYbyZdw3V'); |
|||
assert.strictEqual(hdBread._getExternalWIFByIndex(0), 'L25CoHfqWKR5byQhgp4M8sW1roifBteD3Lj3zCGNcV4JXhbxZ93F'); |
|||
assert.strictEqual(hdBread._getInternalWIFByIndex(0), 'KyEQuB73eueeS7D6iBJrNSvkD1kkdkJoUsavuxGXv5fxWkPJxt96'); |
|||
assert.strictEqual( |
|||
hdBread._getPubkeyByAddress(hdBread._getExternalAddressByIndex(0)).toString('hex'), |
|||
'0354d804a7943eb61ec13deef44586510506889175dc2f3a375867e4796debf2a9', |
|||
); |
|||
assert.strictEqual( |
|||
hdBread._getPubkeyByAddress(hdBread._getInternalAddressByIndex(0)).toString('hex'), |
|||
'02d241fadf3e48ff30a93360f6ef255cc3a797c588c907615d096510a918f46dce', |
|||
); |
|||
|
|||
assert.strictEqual( |
|||
hdBread.getXpub(), |
|||
'xpub68nLLEi3KERQY7jyznC9PQSpSjmekrEmN8324YRCXayMXaavbdEJsK4gEcX2bNf9vGzT4xRks9utZ7ot1CTHLtdyCn9udvv1NWvtY7HXroh', |
|||
); |
|||
}); |
@ -0,0 +1,131 @@ |
|||
/* global it */ |
|||
import { HDLegacyP2PKHWallet } from '../../class'; |
|||
const assert = require('assert'); |
|||
const bitcoin = require('bitcoinjs-lib'); |
|||
|
|||
it('Legacy HD (BIP44) works', async () => { |
|||
if (!process.env.HD_MNEMONIC) { |
|||
console.error('process.env.HD_MNEMONIC not set, skipped'); |
|||
return; |
|||
} |
|||
let hd = new HDLegacyP2PKHWallet(); |
|||
hd.setSecret(process.env.HD_MNEMONIC); |
|||
assert.ok(hd.validateMnemonic()); |
|||
|
|||
assert.strictEqual( |
|||
hd.getXpub(), |
|||
'xpub6ByZUAv558PPheJgcPYHpxPLwz8M7TtueYMAik84NADeQcvbzS8W3WxxJ3C9NzfYkMoChiMAumWbeEvMWhTVpH75NqGv5c9wF3wKDbfQShb', |
|||
); |
|||
|
|||
assert.strictEqual(hd._getExternalAddressByIndex(0), '186FBQmCV5W1xY7ywaWtTZPAQNciVN8Por'); |
|||
assert.strictEqual(hd._getInternalAddressByIndex(0), '1J9zoJz5LsAJ361SQHYnLTWg46Tc2AXUCj'); |
|||
|
|||
assert.strictEqual(hd._getInternalWIFByIndex(0), 'L4ojevRtK81A8Kof3qyLS2M7HvsVDbUDENNhJqU4vf79w9yGnQLb'); |
|||
assert.strictEqual(hd._getExternalWIFByIndex(0), 'Kz6kLhdyDfSbKuVH25XVqBRztjmFe8X22Xe1hnFzEv79gJNMkTAH'); |
|||
|
|||
assert.strictEqual( |
|||
hd._getPubkeyByAddress(hd._getExternalAddressByIndex(0)).toString('hex'), |
|||
'0316e84a2556f30a199541633f5dda6787710ccab26771b7084f4c9e1104f47667', |
|||
); |
|||
assert.strictEqual( |
|||
hd._getPubkeyByAddress(hd._getInternalAddressByIndex(0)).toString('hex'), |
|||
'02ad7b2216f3a2b38d56db8a7ee5c540fd12c4bbb7013106eff78cc2ace65aa002', |
|||
); |
|||
|
|||
assert.strictEqual(hd._getDerivationPathByAddress(hd._getExternalAddressByIndex(0)), "m/84'/0'/0'/0/0"); // wrong, FIXME
|
|||
assert.strictEqual(hd._getDerivationPathByAddress(hd._getInternalAddressByIndex(0)), "m/84'/0'/0'/1/0"); // wrong, FIXME
|
|||
}); |
|||
|
|||
it.only('Legacy HD (BIP44) can create TX', async () => { |
|||
if (!process.env.HD_MNEMONIC) { |
|||
console.error('process.env.HD_MNEMONIC not set, skipped'); |
|||
return; |
|||
} |
|||
let hd = new HDLegacyP2PKHWallet(); |
|||
hd.setSecret(process.env.HD_MNEMONIC); |
|||
assert.ok(hd.validateMnemonic()); |
|||
|
|||
const utxo = [ |
|||
{ |
|||
height: 554830, |
|||
value: 10000, |
|||
address: '186FBQmCV5W1xY7ywaWtTZPAQNciVN8Por', |
|||
txId: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
|||
vout: 0, |
|||
txid: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
|||
amount: 10000, |
|||
wif: 'Kz6kLhdyDfSbKuVH25XVqBRztjmFe8X22Xe1hnFzEv79gJNMkTAH', |
|||
confirmations: 1, |
|||
txhex: |
|||
'01000000000101e8d98effbb4fba4f0a89bcf217eb5a7e2f8efcae44f32ecacbc5d8cc3ce683c301000000171600148ba6d02e74c0a6e000e8b174eb2ed44e5ea211a6ffffffff0510270000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac204e0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac30750000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac409c0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac204716000000000017a914e286d58e53f9247a4710e51232cce0686f16873c8702483045022100af3800cd8171f154785cf13f46c092f61c1668f97db432bb4e7ed7bc812a8c6d022051bddca1eaf1ad8b5f3bd0ccde7447e56fd3c8709e5906f02ec6326e9a5b2ff30121039a421d5eb7c9de6590ae2a471cb556b60de8c6b056beb907dbdc1f5e6092f58800000000', |
|||
}, |
|||
{ |
|||
height: 554830, |
|||
value: 20000, |
|||
address: '1J9zoJz5LsAJ361SQHYnLTWg46Tc2AXUCj', |
|||
txId: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
|||
vout: 1, |
|||
txid: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
|||
amount: 20000, |
|||
wif: 'L4ojevRtK81A8Kof3qyLS2M7HvsVDbUDENNhJqU4vf79w9yGnQLb', |
|||
confirmations: 1, |
|||
txhex: |
|||
'01000000000101e8d98effbb4fba4f0a89bcf217eb5a7e2f8efcae44f32ecacbc5d8cc3ce683c301000000171600148ba6d02e74c0a6e000e8b174eb2ed44e5ea211a6ffffffff0510270000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac204e0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac30750000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac409c0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac204716000000000017a914e286d58e53f9247a4710e51232cce0686f16873c8702483045022100af3800cd8171f154785cf13f46c092f61c1668f97db432bb4e7ed7bc812a8c6d022051bddca1eaf1ad8b5f3bd0ccde7447e56fd3c8709e5906f02ec6326e9a5b2ff30121039a421d5eb7c9de6590ae2a471cb556b60de8c6b056beb907dbdc1f5e6092f58800000000', |
|||
}, |
|||
{ |
|||
height: 554830, |
|||
value: 30000, |
|||
address: '186FBQmCV5W1xY7ywaWtTZPAQNciVN8Por', |
|||
txId: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
|||
vout: 2, |
|||
txid: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
|||
amount: 30000, |
|||
wif: 'Kz6kLhdyDfSbKuVH25XVqBRztjmFe8X22Xe1hnFzEv79gJNMkTAH', |
|||
confirmations: 1, |
|||
txhex: |
|||
'01000000000101e8d98effbb4fba4f0a89bcf217eb5a7e2f8efcae44f32ecacbc5d8cc3ce683c301000000171600148ba6d02e74c0a6e000e8b174eb2ed44e5ea211a6ffffffff0510270000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac204e0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac30750000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac409c0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac204716000000000017a914e286d58e53f9247a4710e51232cce0686f16873c8702483045022100af3800cd8171f154785cf13f46c092f61c1668f97db432bb4e7ed7bc812a8c6d022051bddca1eaf1ad8b5f3bd0ccde7447e56fd3c8709e5906f02ec6326e9a5b2ff30121039a421d5eb7c9de6590ae2a471cb556b60de8c6b056beb907dbdc1f5e6092f58800000000', |
|||
}, |
|||
{ |
|||
height: 554830, |
|||
value: 40000, |
|||
address: '1J9zoJz5LsAJ361SQHYnLTWg46Tc2AXUCj', |
|||
txId: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
|||
vout: 3, |
|||
txid: '4f65c8cb159585c00d4deba9c5b36a2bcdfb1399a561114dcf6f2d0c1174bc5f', |
|||
amount: 40000, |
|||
wif: 'L4ojevRtK81A8Kof3qyLS2M7HvsVDbUDENNhJqU4vf79w9yGnQLb', |
|||
confirmations: 1, |
|||
txhex: |
|||
'01000000000101e8d98effbb4fba4f0a89bcf217eb5a7e2f8efcae44f32ecacbc5d8cc3ce683c301000000171600148ba6d02e74c0a6e000e8b174eb2ed44e5ea211a6ffffffff0510270000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac204e0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac30750000000000001976a9144dc6cbf64df9ab106cee812c7501960b93e9217788ac409c0000000000001976a914bc2db6b74c8db9b188711dcedd511e6a305603f588ac204716000000000017a914e286d58e53f9247a4710e51232cce0686f16873c8702483045022100af3800cd8171f154785cf13f46c092f61c1668f97db432bb4e7ed7bc812a8c6d022051bddca1eaf1ad8b5f3bd0ccde7447e56fd3c8709e5906f02ec6326e9a5b2ff30121039a421d5eb7c9de6590ae2a471cb556b60de8c6b056beb907dbdc1f5e6092f58800000000', |
|||
}, |
|||
]; |
|||
|
|||
let txNew = hd.createTransaction( |
|||
utxo, |
|||
[{ address: '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', value: 80000 }], |
|||
1, |
|||
hd._getInternalAddressByIndex(hd.next_free_change_address_index), |
|||
); |
|||
let tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
|||
assert.strictEqual(tx.ins.length, 4); |
|||
assert.strictEqual(tx.outs.length, 2); |
|||
assert.strictEqual(tx.outs[0].value, 80000); // payee
|
|||
assert.strictEqual(tx.outs[1].value, 19334); // change
|
|||
let toAddress = bitcoin.address.fromOutputScript(tx.outs[0].script); |
|||
let changeAddress = bitcoin.address.fromOutputScript(tx.outs[1].script); |
|||
assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress); |
|||
assert.strictEqual(hd._getInternalAddressByIndex(hd.next_free_change_address_index), changeAddress); |
|||
|
|||
// testing sendMax
|
|||
txNew = hd.createTransaction( |
|||
utxo, |
|||
[{ address: '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK' }], |
|||
1, |
|||
hd._getInternalAddressByIndex(hd.next_free_change_address_index), |
|||
); |
|||
tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
|||
assert.strictEqual(tx.ins.length, 4); |
|||
assert.strictEqual(tx.outs.length, 1); |
|||
toAddress = bitcoin.address.fromOutputScript(tx.outs[0].script); |
|||
assert.strictEqual('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', toAddress); |
|||
}); |
@ -0,0 +1,42 @@ |
|||
/* global it, describe */ |
|||
import { LegacyWallet } from '../../class'; |
|||
const bitcoin = require('bitcoinjs-lib'); |
|||
const assert = require('assert'); |
|||
|
|||
describe('Legacy wallet', () => { |
|||
it('can create transaction', async () => { |
|||
let l = new LegacyWallet(); |
|||
l.setSecret('L4ccWrPMmFDZw4kzAKFqJNxgHANjdy6b7YKNXMwB4xac4FLF3Tov'); |
|||
assert.strictEqual(l.getAddress(), '14YZ6iymQtBVQJk6gKnLCk49UScJK7SH4M'); |
|||
assert.strictEqual(await l.getChangeAddressAsync(), l.getAddress()); |
|||
|
|||
let utxos = [ |
|||
{ |
|||
txid: 'cc44e933a094296d9fe424ad7306f16916253a3d154d52e4f1a757c18242cec4', |
|||
vout: 0, |
|||
value: 100000, |
|||
txhex: |
|||
'0200000000010161890cd52770c150da4d7d190920f43b9f88e7660c565a5a5ad141abb6de09de00000000000000008002a0860100000000001976a91426e01119d265aa980390c49eece923976c218f1588ac3e17000000000000160014c1af8c9dd85e0e55a532a952282604f820746fcd02473044022072b3f28808943c6aa588dd7a4e8f29fad7357a2814e05d6c5d767eb6b307b4e6022067bc6a8df2dbee43c87b8ce9ddd9fe678e00e0f7ae6690d5cb81eca6170c47e8012102e8fba5643e15ab70ec79528833a2c51338c1114c4eebc348a235b1a3e13ab07100000000', |
|||
}, |
|||
]; |
|||
// ^^ only non-segwit inputs need full transaction txhex
|
|||
|
|||
let txNew = l.createTransaction(utxos, [{ value: 90000, address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, l.getAddress()); |
|||
let tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
|||
assert.strictEqual( |
|||
txNew.tx.toHex(), |
|||
'0200000001c4ce4282c157a7f1e4524d153d3a251669f10673ad24e49f6d2994a033e944cc000000006a47304402200faed160757433bcd4d9fe5f55eb92420406e8f3099a7e12ef720c77313c8c7e022044bc9e1abca6a81a8ad5c749f5ec4694301589172b83b1803bc134eda0487dbc01210337c09b3cb889801638078fd4e6998218b28c92d338ea2602720a88847aedceb3ffffffff02905f0100000000001976a914aa381cd428a4e91327fd4434aa0a08ff131f1a5a88ac2f260000000000001976a91426e01119d265aa980390c49eece923976c218f1588ac00000000', |
|||
); |
|||
assert.strictEqual(tx.ins.length, 1); |
|||
assert.strictEqual(tx.outs.length, 2); |
|||
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
|
|||
assert.strictEqual(l.getAddress(), bitcoin.address.fromOutputScript(tx.outs[1].script)); // change address
|
|||
|
|||
// sendMax
|
|||
txNew = l.createTransaction(utxos, [{ address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, l.getAddress()); |
|||
tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
|||
assert.strictEqual(tx.ins.length, 1); |
|||
assert.strictEqual(tx.outs.length, 1); |
|||
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
|
|||
}); |
|||
}); |
@ -0,0 +1,39 @@ |
|||
/* global it, describe */ |
|||
import { SegwitBech32Wallet } from '../../class'; |
|||
const bitcoin = require('bitcoinjs-lib'); |
|||
const assert = require('assert'); |
|||
|
|||
describe('Segwit P2SH wallet', () => { |
|||
it('can create transaction', async () => { |
|||
let wallet = new SegwitBech32Wallet(); |
|||
wallet.setSecret('L4vn2KxgMLrEVpxjfLwxfjnPPQMnx42DCjZJ2H7nN4mdHDyEUWXd'); |
|||
assert.strictEqual(wallet.getAddress(), 'bc1q3rl0mkyk0zrtxfmqn9wpcd3gnaz00yv9yp0hxe'); |
|||
assert.strictEqual(await wallet.getChangeAddressAsync(), wallet.getAddress()); |
|||
|
|||
let utxos = [ |
|||
{ |
|||
txid: '57d18bc076b919583ff074cfba6201edd577f7fe35f69147ea512e970f95ffeb', |
|||
vout: 0, |
|||
value: 100000, |
|||
}, |
|||
]; |
|||
|
|||
let txNew = wallet.createTransaction(utxos, [{ value: 90000, address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, wallet.getAddress()); |
|||
let tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
|||
assert.strictEqual( |
|||
txNew.tx.toHex(), |
|||
'02000000000101ebff950f972e51ea4791f635fef777d5ed0162bacf74f03f5819b976c08bd1570000000000ffffffff02905f0100000000001976a914aa381cd428a4e91327fd4434aa0a08ff131f1a5a88ac2f2600000000000016001488fefdd8967886b32760995c1c36289f44f791850248304502210094d8b9d291b3c131594dbacceebf9277ba598f454acbc2c9fa4a7b20895bb74302201a592c4c121f154be1212e6e6b8cd82bb72b97b0f9c098ce8dbe011fbefc8ac101210314cf2bf53f221e58c5adc1dd95adba9239b248f39b09eb2c550aadc1926fe7aa00000000', |
|||
); |
|||
assert.strictEqual(tx.ins.length, 1); |
|||
assert.strictEqual(tx.outs.length, 2); |
|||
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
|
|||
assert.strictEqual(bitcoin.address.fromOutputScript(tx.outs[1].script), wallet.getAddress()); // change address
|
|||
|
|||
// sendMax
|
|||
txNew = wallet.createTransaction(utxos, [{ address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, wallet.getAddress()); |
|||
tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
|||
assert.strictEqual(tx.ins.length, 1); |
|||
assert.strictEqual(tx.outs.length, 1); |
|||
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
|
|||
}); |
|||
}); |
@ -0,0 +1,39 @@ |
|||
/* global it, describe */ |
|||
import { SegwitP2SHWallet } from '../../class'; |
|||
const bitcoin = require('bitcoinjs-lib'); |
|||
const assert = require('assert'); |
|||
|
|||
describe('Segwit P2SH wallet', () => { |
|||
it('can create transaction', async () => { |
|||
let wallet = new SegwitP2SHWallet(); |
|||
wallet.setSecret('Ky1vhqYGCiCbPd8nmbUeGfwLdXB1h5aGwxHwpXrzYRfY5cTZPDo4'); |
|||
assert.strictEqual(wallet.getAddress(), '3CKN8HTCews4rYJYsyub5hjAVm5g5VFdQJ'); |
|||
assert.strictEqual(await wallet.getChangeAddressAsync(), wallet.getAddress()); |
|||
|
|||
let utxos = [ |
|||
{ |
|||
txid: 'a56b44080cb606c0bd90e77fcd4fb34c863e68e5562e75b4386e611390eb860c', |
|||
vout: 0, |
|||
value: 300000, |
|||
}, |
|||
]; |
|||
|
|||
let txNew = wallet.createTransaction(utxos, [{ value: 90000, address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, wallet.getAddress()); |
|||
let tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
|||
assert.strictEqual( |
|||
txNew.tx.toHex(), |
|||
'020000000001010c86eb9013616e38b4752e56e5683e864cb34fcd7fe790bdc006b60c08446ba50000000017160014139dc70d73097f9d775f8a3280ba3e3435515641ffffffff02905f0100000000001976a914aa381cd428a4e91327fd4434aa0a08ff131f1a5a88ac6f3303000000000017a914749118baa93fb4b88c28909c8bf0a8202a0484f487024730440220086b55a771f37daadbe64fe557a32fd68ee92995445af0b0a5b9343db67505e1022064c9a9778a19a0276761af69b8917d19ed4b791c785dd8cb4aae327f2a6b526f012103a5de146762f84055db3202c1316cd9008f16047f4f408c1482fdb108217eda0800000000', |
|||
); |
|||
assert.strictEqual(tx.ins.length, 1); |
|||
assert.strictEqual(tx.outs.length, 2); |
|||
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
|
|||
assert.strictEqual(bitcoin.address.fromOutputScript(tx.outs[1].script), wallet.getAddress()); // change address
|
|||
|
|||
// sendMax
|
|||
txNew = wallet.createTransaction(utxos, [{ address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, wallet.getAddress()); |
|||
tx = bitcoin.Transaction.fromHex(txNew.tx.toHex()); |
|||
assert.strictEqual(tx.ins.length, 1); |
|||
assert.strictEqual(tx.outs.length, 1); |
|||
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
|
|||
}); |
|||
}); |
@ -1,282 +0,0 @@ |
|||
/* global describe, it */ |
|||
const bitcoinjs = require('bitcoinjs-lib'); |
|||
let assert = require('assert'); |
|||
|
|||
describe('unit - signer', function() { |
|||
describe('createSegwitTransaction()', function() { |
|||
it('should return valid tx hex for segwit transactions', function(done) { |
|||
let signer = require('../../models/signer'); |
|||
let utxos = [ |
|||
{ |
|||
txid: '1e1a8cced5580eecd0ac15845fc3adfafbb0f5944a54950e4a16b8f6d1e9b715', |
|||
vout: 1, |
|||
address: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|||
account: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|||
scriptPubKey: 'a9146fbf1cee74734503297e46a0db3e3fbb06f2e9d387', |
|||
amount: 0.001, |
|||
confirmations: 108, |
|||
spendable: false, |
|||
solvable: false, |
|||
safe: true, |
|||
}, |
|||
]; |
|||
let tx = signer.createSegwitTransaction( |
|||
utxos, |
|||
'1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr', |
|||
0.001, |
|||
0.0001, |
|||
'KyWpryAKPiXXbipxWhtprZjSLVjp22sxbVnJssq2TCNQxs1SuMeD', |
|||
); |
|||
assert.strictEqual( |
|||
tx, |
|||
'0100000000010115b7e9d1f6b8164a0e95544a94f5b0fbfaadc35f8415acd0ec0e58d5ce8c1a1e0100000017160014f90e5bca5635b84bd828064586bd7eb117fee9a9ffffffff01905f0100000000001976a914f7c6c1f9f6142107ed293c8fbf85fbc49eb5f1b988ac02473044022023eef496f43936550e08898d10b254ee910dfd19268341edb2f61b873ccba25502204b722787fabc37c2c9e9575832331b0ba0c3f7cd0c18a6fb90027f4327bd8d850121039425479ea581ebc7f55959da8c2e1a1063491768860386335dd4630b5eeacfc500000000', |
|||
); |
|||
done(); |
|||
}); |
|||
|
|||
it('should return valid tx hex for RBF-able segwit transactions', function(done) { |
|||
let signer = require('../../models/signer'); |
|||
let utxos = [ |
|||
{ |
|||
txid: '1e1a8cced5580eecd0ac15845fc3adfafbb0f5944a54950e4a16b8f6d1e9b715', |
|||
vout: 1, |
|||
address: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|||
account: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|||
scriptPubKey: 'a9146fbf1cee74734503297e46a0db3e3fbb06f2e9d387', |
|||
amount: 0.1, |
|||
confirmations: 108, |
|||
spendable: false, |
|||
solvable: false, |
|||
safe: true, |
|||
}, |
|||
]; |
|||
let txhex = signer.createSegwitTransaction( |
|||
utxos, |
|||
'1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr', |
|||
0.001, |
|||
0.0001, |
|||
'KyWpryAKPiXXbipxWhtprZjSLVjp22sxbVnJssq2TCNQxs1SuMeD', |
|||
'3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|||
0, |
|||
); |
|||
assert.strictEqual( |
|||
txhex, |
|||
'0100000000010115b7e9d1f6b8164a0e95544a94f5b0fbfaadc35f8415acd0ec0e58d5ce8c1a1e0100000017160014f90e5bca5635b84bd828064586bd7eb117fee9a90000000002905f0100000000001976a914f7c6c1f9f6142107ed293c8fbf85fbc49eb5f1b988ace00f97000000000017a9146fbf1cee74734503297e46a0db3e3fbb06f2e9d38702483045022100bd687693e57161282a80affb82f18386cbf319bca72ca2c16320b0f3b087bee802205e22a9a16b86628ea08eab83aebec1348c476e9d0c90cd41aa73c47f50d86aab0121039425479ea581ebc7f55959da8c2e1a1063491768860386335dd4630b5eeacfc500000000', |
|||
); |
|||
// now, testing change addess, destination address, amounts & fees...
|
|||
let tx = bitcoinjs.Transaction.fromHex(txhex); |
|||
assert.strictEqual(bitcoinjs.address.fromOutputScript(tx.outs[0].script), '1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr'); |
|||
assert.strictEqual(bitcoinjs.address.fromOutputScript(tx.outs[1].script), '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi'); |
|||
assert.strictEqual(tx.outs[0].value, 90000); // 0.0009 because we deducted fee 0.0001
|
|||
assert.strictEqual(tx.outs[1].value, 9900000); // 0.099 because 0.1 - 0.001
|
|||
done(); |
|||
}); |
|||
|
|||
it('should create Replace-By-Fee tx, given txhex', () => { |
|||
let txhex = |
|||
'0100000000010115b7e9d1f6b8164a0e95544a94f5b0fbfaadc35f8415acd0ec0e58d5ce8c1a1e0100000017160014f90e5bca5635b84bd828064586bd7eb117fee9a90000000002905f0100000000001976a914f7c6c1f9f6142107ed293c8fbf85fbc49eb5f1b988ace00f97000000000017a9146fbf1cee74734503297e46a0db3e3fbb06f2e9d38702483045022100bd687693e57161282a80affb82f18386cbf319bca72ca2c16320b0f3b087bee802205e22a9a16b86628ea08eab83aebec1348c476e9d0c90cd41aa73c47f50d86aab0121039425479ea581ebc7f55959da8c2e1a1063491768860386335dd4630b5eeacfc500000000'; |
|||
let signer = require('../../models/signer'); |
|||
let dummyUtxodata = { |
|||
'1e1a8cced5580eecd0ac15845fc3adfafbb0f5944a54950e4a16b8f6d1e9b715': { |
|||
// txid we use output from
|
|||
1: 10000000, // output index and it's value in satoshi
|
|||
}, |
|||
}; |
|||
let newhex = signer.createRBFSegwitTransaction( |
|||
txhex, |
|||
{ '1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr': '3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2' }, |
|||
0.0001, |
|||
'KyWpryAKPiXXbipxWhtprZjSLVjp22sxbVnJssq2TCNQxs1SuMeD', |
|||
dummyUtxodata, |
|||
); |
|||
let oldTx = bitcoinjs.Transaction.fromHex(txhex); |
|||
let newTx = bitcoinjs.Transaction.fromHex(newhex); |
|||
// just checking old tx...
|
|||
assert.strictEqual(bitcoinjs.address.fromOutputScript(oldTx.outs[0].script), '1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr'); // old DESTINATION address
|
|||
assert.strictEqual(bitcoinjs.address.fromOutputScript(oldTx.outs[1].script), '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi'); // old CHANGE address
|
|||
assert.strictEqual(oldTx.outs[0].value, 90000); // 0.0009 because we deducted fee 0.0001
|
|||
assert.strictEqual(oldTx.outs[1].value, 9900000); // 0.099 because 0.1 - 0.001
|
|||
// finaly, new tx checks...
|
|||
assert.strictEqual(oldTx.outs[0].value, newTx.outs[0].value); // DESTINATION output amount remains unchanged
|
|||
assert.strictEqual(oldTx.outs[1].value - newTx.outs[1].value, 0.0001 * 100000000); // CHANGE output decreased on the amount of fee delta
|
|||
assert.strictEqual(bitcoinjs.address.fromOutputScript(newTx.outs[0].script), '3BDsBDxDimYgNZzsqszNZobqQq3yeUoJf2'); // new DESTINATION address
|
|||
assert.strictEqual(bitcoinjs.address.fromOutputScript(newTx.outs[1].script), '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi'); // CHANGE address remains
|
|||
assert.strictEqual(oldTx.ins[0].sequence + 1, newTx.ins[0].sequence); |
|||
}); |
|||
|
|||
it('should return valid tx hex for segwit transactions with multiple inputs', function(done) { |
|||
let signer = require('../../models/signer'); |
|||
let utxos = [ |
|||
{ |
|||
txid: '4e2a536aaf6b0b8a4f439d0343436cd321b8bac9840a24d13b8eed484a257b0b', |
|||
vout: 0, |
|||
address: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|||
account: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|||
scriptPubKey: 'a914e0d81f03546ab8f29392b488ec62ab355ee7c57387', |
|||
amount: 0.0009, |
|||
confirmations: 67, |
|||
spendable: false, |
|||
solvable: false, |
|||
safe: true, |
|||
}, |
|||
{ |
|||
txid: '09e1b78d4ecd95dd4c7dbc840a2619da6d02caa345a63b2733f3972666462fbd', |
|||
vout: 0, |
|||
address: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|||
account: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|||
scriptPubKey: 'a914e0d81f03546ab8f29392b488ec62ab355ee7c57387', |
|||
amount: 0.0019, |
|||
confirmations: 142, |
|||
spendable: false, |
|||
solvable: false, |
|||
safe: true, |
|||
}, |
|||
]; |
|||
let tx = signer.createSegwitTransaction( |
|||
utxos, |
|||
'1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr', |
|||
0.0028, |
|||
0.0002, |
|||
'L4iRvejJG9gRhKVc3rZm5haoyd4EuCi77G91DnXRrvNDqiXktkXh', |
|||
); |
|||
assert.strictEqual( |
|||
tx, |
|||
'010000000001020b7b254a48ed8e3bd1240a84c9bab821d36c4343039d434f8a0b6baf6a532a4e00000000171600141e16a923b1a9e8d0c2a044030608a6aa13f97e9affffffffbd2f46662697f333273ba645a3ca026dda19260a84bc7d4cdd95cd4e8db7e10900000000171600141e16a923b1a9e8d0c2a044030608a6aa13f97e9affffffff01a0f70300000000001976a914f7c6c1f9f6142107ed293c8fbf85fbc49eb5f1b988ac02483045022100b3e001b880a7a18294640165cc40c777669534803cee7206c8d3f03531bb315502204642a4569576a2e9e77342c7a9aaa508a21248b7720fe0f9e6d76713951c133001210314389c888e9669ae05739819fc7c43d7a50fdeabd2a8951f9607c8cad394fd4b02473044022078bd4f47178ce13c4fbf77c5ce78c80ac10251aa053c68c8febb21ce228f844e02207b02bdd754fbc2df9f62ea98e7dbd6c43e760b8f78c7c00b43512a06b498adb501210314389c888e9669ae05739819fc7c43d7a50fdeabd2a8951f9607c8cad394fd4b00000000', |
|||
); |
|||
done(); |
|||
}); |
|||
|
|||
it('should return valid tx hex for segwit transactions with change address', function(done) { |
|||
let signer = require('../../models/signer'); |
|||
let utxos = [ |
|||
{ |
|||
txid: '160559030484800a77f9b38717bb0217e87bfeb47b92e2e5bad6316ad9d8d360', |
|||
vout: 1, |
|||
address: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|||
account: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|||
scriptPubKey: 'a914e0d81f03546ab8f29392b488ec62ab355ee7c57387', |
|||
amount: 0.004, |
|||
confirmations: 271, |
|||
spendable: false, |
|||
solvable: false, |
|||
safe: true, |
|||
}, |
|||
]; |
|||
let tx = signer.createSegwitTransaction( |
|||
utxos, |
|||
'1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr', |
|||
0.002, |
|||
0.0001, |
|||
'L4iRvejJG9gRhKVc3rZm5haoyd4EuCi77G91DnXRrvNDqiXktkXh', |
|||
); |
|||
assert.strictEqual( |
|||
tx, |
|||
'0100000000010160d3d8d96a31d6bae5e2927bb4fe7be81702bb1787b3f9770a8084040359051601000000171600141e16a923b1a9e8d0c2a044030608a6aa13f97e9affffffff0230e60200000000001976a914f7c6c1f9f6142107ed293c8fbf85fbc49eb5f1b988ac400d03000000000017a914e0d81f03546ab8f29392b488ec62ab355ee7c573870247304402202c962e14ae6abd45dc9613d2f088ad487e805670548e244deb25d762b310a60002204f12c7f9b8da3567b39906ff6c46b27ce087e7ae91bbe34fb1cdee1b994b9d3001210314389c888e9669ae05739819fc7c43d7a50fdeabd2a8951f9607c8cad394fd4b00000000', |
|||
); |
|||
done(); |
|||
}); |
|||
|
|||
it('should return valid tx hex for segwit transactions if change is too small so it causes @dust error', function(done) { |
|||
// checking that change amount is at least 3x of fee, otherwise screw the change, just add it to fee
|
|||
let signer = require('../../models/signer'); |
|||
let utxos = [ |
|||
{ |
|||
txid: '160559030484800a77f9b38717bb0217e87bfeb47b92e2e5bad6316ad9d8d360', |
|||
vout: 1, |
|||
address: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|||
account: '3NBtBset4qPD8DZeLw4QbFi6SNjNL8hg7x', |
|||
scriptPubKey: 'a914e0d81f03546ab8f29392b488ec62ab355ee7c57387', |
|||
amount: 0.004, |
|||
confirmations: 271, |
|||
spendable: false, |
|||
solvable: false, |
|||
safe: true, |
|||
}, |
|||
]; |
|||
let txhex = signer.createSegwitTransaction( |
|||
utxos, |
|||
'1Pb81K1xJnMjUfFgKUbva6gr1HCHXxHVnr', |
|||
0.003998, |
|||
0.000001, |
|||
'L4iRvejJG9gRhKVc3rZm5haoyd4EuCi77G91DnXRrvNDqiXktkXh', |
|||
); |
|||
let bitcoin = bitcoinjs; |
|||
let tx = bitcoin.Transaction.fromHex(txhex); |
|||
assert.strictEqual(tx.ins.length, 1); |
|||
assert.strictEqual(tx.outs.length, 1); // only 1 output, which means change is neglected
|
|||
assert.strictEqual(tx.outs[0].value, 399700); |
|||
done(); |
|||
}); |
|||
}); |
|||
|
|||
describe('WIF2address()', function() { |
|||
it('should convert WIF to segwit P2SH address', function(done) { |
|||
let signer = require('../../models/signer'); |
|||
let address = signer.WIF2segwitAddress('L55uHs7pyz7rP18K38kB7kqDVNJaeYFzJtZyC3ZjD2c684dzXQWs'); |
|||
assert.strictEqual('3FSL9x8P8cQ74iW2HLP6JPGPRgc4K2FnsU', address); |
|||
done(); |
|||
}); |
|||
}); |
|||
|
|||
describe('generateNewAddress()', function() { |
|||
it('should generate new address', function(done) { |
|||
let signer = require('../../models/signer'); |
|||
let address = signer.generateNewSegwitAddress(); |
|||
assert.ok(address.WIF); |
|||
assert.ok(address.address); |
|||
assert.strictEqual(address.address, signer.WIF2segwitAddress(address.WIF)); |
|||
done(); |
|||
}); |
|||
}); |
|||
|
|||
describe('URI()', function() { |
|||
it('should form correct payment url', function(done) { |
|||
let signer = require('../../models/signer'); |
|||
let url = signer.URI({ |
|||
address: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|||
message: 'For goods & services', |
|||
label: 'nolabel', |
|||
amount: 1000000, |
|||
}); |
|||
assert.strictEqual(url, 'bitcoin:3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi?amount=0.01&message=For%20goods%20%26%20services&label=nolabel'); |
|||
|
|||
url = signer.URI({ |
|||
address: '1DzJepHCRD2C9vpFjk11eXJi97juEZ3ftv', |
|||
message: 'wheres the money lebowski', |
|||
amount: 400000, |
|||
}); |
|||
assert.strictEqual(url, 'bitcoin:1DzJepHCRD2C9vpFjk11eXJi97juEZ3ftv?amount=0.004&message=wheres%20the%20money%20lebowski'); |
|||
done(); |
|||
}); |
|||
}); |
|||
|
|||
describe('createTransaction()', () => { |
|||
const signer = require('../../models/signer'); |
|||
it('should return valid TX hex for legacy transactions', () => { |
|||
let utxos = [ |
|||
{ |
|||
txid: '2f445cf016fa2772db7d473bff97515355b4e6148e1c980ce351d47cf54c517f', |
|||
vout: 1, |
|||
address: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|||
account: '3Bsssbs4ANCGNETvGLJ3Fvri6SiVnH1fbi', |
|||
scriptPubKey: 'a9146fbf1cee74734503297e46a0db3e3fbb06f2e9d387', |
|||
amount: 0.01, |
|||
confirmations: 108, |
|||
spendable: false, |
|||
solvable: false, |
|||
safe: true, |
|||
}, |
|||
]; |
|||
let toAddr = '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB'; |
|||
let amount = 0.001; |
|||
let fee = 0.0001; |
|||
let WIF = 'KzbTHhzzZyVhkTYpuReMBkE7zUvvDEZtavq1DJV85MtBZyHK1TTF'; |
|||
let fromAddr = '179JSjDc9Dh9pWWq9qv35sZsXQAV6VdE1E'; |
|||
let txHex = signer.createTransaction(utxos, toAddr, amount, fee, WIF, fromAddr); |
|||
assert.strictEqual( |
|||
txHex, |
|||
'01000000017f514cf57cd451e30c981c8e14e6b455535197ff3b477ddb7227fa16f05c442f010000006b483045022100c5d6b024db144aa1f0cb6d6212c326c9753f4144fd69947c1f38657944b92022022039214118b745afe6e031f96f3e98e705979f2b9f9cbbc6a91e11c89c811a3292012103f5438d524ad1cc288963466d6ef1a27d83183f7e9b7fe30879ecdae887692a31ffffffff02905f0100000000001976a914aa381cd428a4e91327fd4434aa0a08ff131f1a5a88aca0bb0d00000000001976a9144362a4c0dbf5102238164d1ec97f3b518bb651cd88ac00000000', |
|||
); |
|||
}); |
|||
}); |
|||
}); |
Loading…
Reference in new issue