-
Notifications
You must be signed in to change notification settings - Fork 45
/
index.js
157 lines (150 loc) · 4.85 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
const mingo = require("mingo")
const _Buffer = require('buffer/')
const bitcoin = require('bsv');
const explorer = require('bitcore-explorers');
const defaults = {
rpc: "api.mattercloud.net",
fee: 400,
feeb: 1.4
}
// The end goal of 'build' is to create a hex formated transaction object
// therefore this function must end with _tx() for all cases
// and return a hex formatted string of either a tranaction or a script
var build = function(options, callback) {
let script = null;
let rpcaddr = (options.pay && options.pay.rpc) ? options.pay.rpc : defaults.rpc;
if (options.tx) {
// if tx exists, check to see if it's already been signed.
// if it's a signed transaction
// and the request is trying to override using 'data' or 'pay',
// we should throw an error
let tx = new bitcoin.Transaction(options.tx)
// transaction is already signed
if (tx.inputs.length > 0 && tx.inputs[0].script) {
if (options.pay || options.data) {
callback(new Error("the transaction is already signed and cannot be modified"))
return;
}
}
} else {
// construct script only if transaction doesn't exist
// if a 'transaction' attribute exists, the 'data' should be ignored to avoid confusion
if (options.data) {
script = _script(options)
}
}
// Instantiate pay
if (options.pay && options.pay.key) {
// key exists => create a signed transaction
let key = options.pay.key;
const privateKey = new bitcoin.PrivateKey(key);
const address = privateKey.toAddress();
const insight = new explorer.Insight(rpcaddr)
insight.getUnspentUtxos(address, function (err, res) {
if (err) {
callback(err);
return;
}
if (options.pay.filter && options.pay.filter.q && options.pay.filter.q.find) {
let f = new mingo.Query(options.pay.filter.q.find)
res = res.filter(function(item) {
return f.test(item)
})
}
let tx = new bitcoin.Transaction(options.tx).from(res);
if (script) {
tx.addOutput(new bitcoin.Transaction.Output({ script: script, satoshis: 0 }));
}
if (options.pay.to && Array.isArray(options.pay.to)) {
options.pay.to.forEach(function(receiver) {
tx.to(receiver.address, receiver.value)
})
}
tx.fee(defaults.fee).change(address);
let opt_pay = options.pay || {};
let myfee = opt_pay.fee || Math.ceil(tx._estimateSize()* (opt_pay.feeb || defaults.feeb));
tx.fee(myfee);
//Check all the outputs for dust
for(var i=0;i<tx.outputs.length;i++){
if(tx.outputs[i]._satoshis>0 && tx.outputs[i]._satoshis<546){
tx.outputs.splice(i,1);
i--;
}
}
let transaction = tx.sign(privateKey);
callback(null, transaction);
})
} else {
// key doesn't exist => create an unsigned transaction
let fee = (options.pay && options.pay.fee) ? options.pay.fee : defaults.fee;
let tx = new bitcoin.Transaction(options.tx).fee(fee);
if (script) {
tx.addOutput(new bitcoin.Transaction.Output({ script: script, satoshis: 0 }));
}
callback(null, tx)
}
}
var send = function(options, callback) {
if (!callback) {
callback = function() {};
}
build(options, function(err, tx) {
if (err) {
callback(err);
return;
}
let rpcaddr = (options.pay && options.pay.rpc) ? options.pay.rpc : defaults.rpc;
const insight = new explorer.Insight(rpcaddr)
insight.broadcast(tx.toString(), callback)
})
}
// compose script
var _script = function(options) {
var s = null;
if (options.data) {
if (Array.isArray(options.data)) {
s = new bitcoin.Script();
if (!options.hasOwnProperty("safe")) {
options.safe = true;
}
if (options.safe) {
s.add(bitcoin.Opcode.OP_FALSE);
}
// Add op_return
s.add(bitcoin.Opcode.OP_RETURN);
options.data.forEach(function(item) {
// add push data
if (item.constructor.name === 'ArrayBuffer') {
let buffer = _Buffer.Buffer.from(item)
s.add(buffer)
} else if (item.constructor.name === 'Buffer') {
s.add(item)
} else if (typeof item === 'string') {
if (/^0x/i.test(item)) {
// ex: 0x6d02
s.add(Buffer.from(item.slice(2), "hex"))
} else {
// ex: "hello"
s.add(Buffer.from(item))
}
} else if (typeof item === 'object' && item.hasOwnProperty('op')) {
s.add({ opcodenum: item.op })
}
})
} else if (typeof options.data === 'string') {
// Exported transaction
s = bitcoin.Script.fromHex(options.data);
}
}
return s;
}
var connect = function(endpoint) {
var rpc = endpoint ? endpoint : defaults.rpc;
return new explorer.Insight(rpc);
}
module.exports = {
build: build,
send: send,
bsv: bitcoin,
connect: connect,
}