Untitled diff

Created Diff never expires
4 removals
125 lines
19 additions
141 lines
const bch = require('bitcore-lib-cash');
const bch = require('bitcore-lib-cash');
const explorer = require('bitcore-explorers');
const explorer = require('bitcore-explorers');
const defaults = {
const defaults = {
excludeOpCodes: [],
rpc: "https://cashexplorer.bitcoin.com",
rpc: "https://cashexplorer.bitcoin.com",
fee: 400
fee: 400
}
}
// The end goal of 'build' is to create a hex formated transaction object
// The end goal of 'build' is to create a hex formated transaction object
// therefore this function must end with _tx() for all cases
// therefore this function must end with _tx() for all cases
// and return a hex formatted string of either a tranaction or a script
// and return a hex formatted string of either a tranaction or a script
var build = function(options, callback) {
var build = function(options, callback) {
let script = null;
let script = null;
let excludeOpCodes = (options.cash && options.cash.excludeOpCodes) ? options.cash.excludeOpCodes : defaults.excludeOpCodes;
let rpcaddr = (options.cash && options.cash.rpc) ? options.cash.rpc : defaults.rpc;
let rpcaddr = (options.cash && options.cash.rpc) ? options.cash.rpc : defaults.rpc;
if (options.tx) {
if (options.tx) {
// if tx exists, check to see if it's already been signed.
// if tx exists, check to see if it's already been signed.
// if it's a signed transaction
// if it's a signed transaction
// and the request is trying to override using 'data' or 'cash',
// and the request is trying to override using 'data' or 'cash',
// we should throw an error
// we should throw an error
let tx = new bch.Transaction(options.tx)
let tx = new bch.Transaction(options.tx)
// transaction is already signed
// transaction is already signed
if (tx.inputs.length > 0 && tx.inputs[0].script) {
if (tx.inputs.length > 0 && tx.inputs[0].script) {
if (options.cash || options.data) {
if (options.cash || options.data) {
callback(new Error("the transaction is already signed and cannot be modified"))
callback(new Error("the transaction is already signed and cannot be modified"))
return;
return;
}
}
}
}
} else {
} else {
// construct script only if transaction doesn't exist
// construct script only if transaction doesn't exist
// if a 'transaction' attribute exists, the 'data' should be ignored to avoid confusion
// if a 'transaction' attribute exists, the 'data' should be ignored to avoid confusion
if (options.data) {
if (options.data) {
script = _script(options)
script = _script(options)
}
}
}
}
// Instantiate cash
// Instantiate cash
if (options.cash && options.cash.key) {
if (options.cash && options.cash.key) {
// key exists => create a signed transaction
// key exists => create a signed transaction
let key = options.cash.key;
let key = options.cash.key;
const privateKey = new bch.PrivateKey(key);
const privateKey = new bch.PrivateKey(key);
const address = privateKey.toAddress();
const address = privateKey.toAddress();
const insight = new explorer.Insight(rpcaddr)
const insight = new explorer.Insight(rpcaddr)
insight.getUnspentUtxos(address, function (err, res) {
insight.getUnspentUtxos(address, function (err, res) {
let tx = new bch.Transaction(options.tx).from(res);
let unspentUtxos = []
for (let i = 0; i < res.length; ++i) {
let opCodeFound = false
for (let j = 0; j < res[i].script.chunks.length; ++j) {
if (options.cash.excludeOpCodes.includes(parseInt(res[i].script.chunks[j].opcodenum))) {
opCodeFound = true
break
}
}

if (!opCodeFound) {
unspentUtxos.push(res[i])
}
}

let tx = new bch.Transaction(options.tx).from(unspentUtxos);
if (script) {
if (script) {
tx.addOutput(new bch.Transaction.Output({ script: script, satoshis: 0 }));
tx.addOutput(new bch.Transaction.Output({ script: script, satoshis: 0 }));
}
}
if (options.cash.to && Array.isArray(options.cash.to)) {
if (options.cash.to && Array.isArray(options.cash.to)) {
options.cash.to.forEach(function(receiver) {
options.cash.to.forEach(function(receiver) {
tx.to(receiver.address, receiver.value)
tx.to(receiver.address, receiver.value)
})
})
}
}


tx.fee(defaults.fee).change(address);
tx.fee(defaults.fee).change(address);
if (options.cash && options.cash.fee) {
if (options.cash && options.cash.fee) {
tx.fee(options.cash.fee)
tx.fee(options.cash.fee)
} else {
} else {
var estSize=Math.ceil(tx._estimateSize()*1.4);
var estSize=Math.ceil(tx._estimateSize()*1.4);
tx.fee(estSize);
tx.fee(estSize);
}
}


//Check all the outputs for dust
//Check all the outputs for dust
for(var i=0;i<tx.outputs.length;i++){
for(var i=0;i<tx.outputs.length;i++){
if(tx.outputs[i]._satoshis>0 && tx.outputs[i]._satoshis<546){
if(tx.outputs[i]._satoshis>0 && tx.outputs[i]._satoshis<546){
tx.outputs.splice(i,1);
tx.outputs.splice(i,1);
i--;
i--;
}
}
}
}
let transaction = tx.sign(privateKey);
let transaction = tx.sign(privateKey);
callback(null, transaction);
callback(null, transaction);
})
})
} else {
} else {
// key doesn't exist => create an unsigned transaction
// key doesn't exist => create an unsigned transaction
let fee = (options.cash && options.cash.fee) ? options.cash.fee : defaults.fee;
let fee = (options.cash && options.cash.fee) ? options.cash.fee : defaults.fee;
let tx = new bch.Transaction(options.tx).fee(fee);
let tx = new bch.Transaction(options.tx).fee(fee);
if (script) {
if (script) {
tx.addOutput(new bch.Transaction.Output({ script: script, satoshis: 0 }));
tx.addOutput(new bch.Transaction.Output({ script: script, satoshis: 0 }));
}
}
callback(null, tx)
callback(null, tx)
}
}
}
}
var send = function(options, callback) {
var send = function(options, callback) {
build(options, function(err, tx) {
build(options, function(err, tx) {
let rpcaddr = (options.cash && options.cash.rpc) ? options.cash.rpc : defaults.rpc;
let rpcaddr = (options.cash && options.cash.rpc) ? options.cash.rpc : defaults.rpc;
const insight = new explorer.Insight(rpcaddr)
const insight = new explorer.Insight(rpcaddr)
if (callback) {
if (callback) {
insight.broadcast(tx.toString(), callback)
insight.broadcast(tx.toString(), callback)
} else {
} else {
insight.broadcast(tx.toString(), function() { })
insight.broadcast(tx.toString(), function() { })
}
}
})
})
}
}
// compose script
// compose script
var _script = function(options) {
var _script = function(options) {
var s = null;
var s = null;
if (options.data) {
if (options.data) {
if (Array.isArray(options.data)) {
if (Array.isArray(options.data)) {
s = new bch.Script();
s = new bch.Script();
// Add op_return
// Add op_return
s.add(bch.Opcode.OP_RETURN);
s.add(bch.Opcode.OP_RETURN);
options.data.forEach(function(item) {
options.data.forEach(function(item) {
// add push data
// add push data
if (/^0x/i.test(item)) {
if (/^0x/i.test(item)) {
// ex: 0x6d02
// ex: 0x6d02
s.add(Buffer.from(item.slice(2), "hex"))
s.add(Buffer.from(item.slice(2), "hex"))
} else {
} else {
// ex: "hello"
// ex: "hello"
s.add(Buffer.from(item))
s.add(Buffer.from(item))
}
}
})
})
} else if (typeof options.data === 'string') {
} else if (typeof options.data === 'string') {
// Exported transaction
// Exported transaction
s = bch.Script.fromHex(options.data);
s = bch.Script.fromHex(options.data);
}
}
}
}
return s;
return s;
}
}
var connect = function(endpoint) {
var connect = function(endpoint) {
var rpc = endpoint ? endpoint : defaults.rpc;
var rpc = endpoint ? endpoint : defaults.rpc;
return new explorer.Insight(rpc);
return new explorer.Insight(rpc);
}
}
module.exports = {
module.exports = {
build: build,
build: build,
send: send,
send: send,
bch: bch,
bch: bch,
connect: connect,
connect: connect,
}
}