1 | var bitcoin = require('bitcoinjs-lib')
|
2 |
|
3 | var headerHex = 'e299a5'
|
4 |
|
5 | var signFromTransactionHex = function (signTransactionHex) {
|
6 | if (!signTransactionHex) {
|
7 | return false
|
8 | }
|
9 | return function (tx, callback) {
|
10 | var txHex = tx.tx.toHex()
|
11 | signTransactionHex(txHex, function (error, signedTxHex) {
|
12 | var signedTx = bitcoin.TransactionBuilder.fromTransaction(bitcoin.Transaction.fromHex(signedTxHex))
|
13 | callback(error, signedTx)
|
14 | })
|
15 | }
|
16 | }
|
17 |
|
18 | var create = function (options, callback) {
|
19 | var commonWallet = options.commonWallet
|
20 | var commonBlockchain = options.commonBlockchain
|
21 | var openpublishSha1 = options.openpublishSha1
|
22 | var tipDestinationAddress = options.tipDestinationAddress
|
23 | var tipAmount = options.tipAmount || 10000
|
24 | var data = new Buffer(headerHex + openpublishSha1, 'hex')
|
25 | var signTransaction = signFromTransactionHex(commonWallet.signRawTransaction)
|
26 | options.signTransaction = signTransaction
|
27 | var address = commonWallet.address
|
28 | var fee = options.fee || 1000
|
29 | var payloadScript = bitcoin.Script.fromChunks([bitcoin.opcodes.OP_RETURN, data])
|
30 | var tx = new bitcoin.TransactionBuilder()
|
31 | commonBlockchain.Addresses.Unspents([address], function (err, addresses_unspents) {
|
32 | if (err || addresses_unspents.length === 0) {
|
33 | callback('error: possibly no unspents associated with address', null)
|
34 | } else {
|
35 | var unspentOutputs = addresses_unspents[0]
|
36 | var compare = function (a, b) {
|
37 | if (a.value < b.value) {
|
38 | return -1
|
39 | }
|
40 | if (a.value > b.value) {
|
41 | return 1
|
42 | }
|
43 | return 0
|
44 | }
|
45 | unspentOutputs.sort(compare)
|
46 | var unspentValue = 0
|
47 | for (var i = unspentOutputs.length - 1; i >= 0; i--) {
|
48 | var unspentOutput = unspentOutputs[i]
|
49 | if (unspentOutput.value === 0) {
|
50 | continue
|
51 | }
|
52 | unspentValue += unspentOutput.value
|
53 | tx.addInput(unspentOutput.txid, unspentOutput.vout)
|
54 | if (unspentValue - fee - tipAmount >= 0) {
|
55 | break
|
56 | }
|
57 | }
|
58 | tx.addOutput(payloadScript, 0)
|
59 | tx.addOutput(tipDestinationAddress, tipAmount)
|
60 |
|
61 | if (unspentValue - fee - tipAmount > 0) {
|
62 | tx.addOutput(address, unspentValue - fee - tipAmount)
|
63 | }
|
64 |
|
65 | signTransaction(tx, function (err, signedTx) {
|
66 | if (err) { }
|
67 | var signedTxBuilt = signedTx.build()
|
68 | var signedTxHex = signedTxBuilt.toHex()
|
69 | var txid = signedTxBuilt.getId()
|
70 | callback(false, signedTxHex, txid)
|
71 | })
|
72 | }
|
73 | })
|
74 | }
|
75 |
|
76 | var scanSingle = function (options, callback) {
|
77 | if (options.tx) {
|
78 | return scan({transactions: [options.tx]}, function (err, tips) {
|
79 | if (err) { }
|
80 | callback(err, tips[0])
|
81 | })
|
82 | } else {
|
83 | var txid = options.txid
|
84 | var commonBlockchain = options.commonBlockchain
|
85 | return commonBlockchain.Transactions.Get([txid], function (err, txs) {
|
86 | if (err) { }
|
87 | var tx = txs[0]
|
88 | scan({transactions: [tx]}, function (err, tips) {
|
89 | callback(err, tips[0])
|
90 | })
|
91 | })
|
92 | }
|
93 | }
|
94 |
|
95 | var scan = function (options, callback) {
|
96 | var transactions = options.transactions
|
97 | var tips = []
|
98 | transactions.forEach(function (tx) {
|
99 | if (!tx) {
|
100 | return
|
101 | }
|
102 | var tip = {}
|
103 | var sources = []
|
104 | var value
|
105 | var tipDestinationAddresses = []
|
106 | var tipAmount = 0
|
107 | tx.vin.forEach(function (input) {
|
108 | var sourceAddress = input.addresses[0]
|
109 | if (sourceAddress) {
|
110 | sources.push(sourceAddress)
|
111 | }
|
112 | })
|
113 | tx.vout.forEach(function (output) {
|
114 | if (output.scriptPubKey.type === 'nulldata') {
|
115 | var scriptPubKey = output.scriptPubKey.hex
|
116 | if (scriptPubKey.slice(0, 2) === '6a') {
|
117 | var data = scriptPubKey.slice(4, 84)
|
118 | if (data.slice(0, 6) === headerHex && data.length === 46) {
|
119 | tip.openpublishSha1 = data.slice(6, 46)
|
120 | }
|
121 | }
|
122 | } else if (output.scriptPubKey.type === 'pubkeyhash') {
|
123 | var destinationAddress = output.scriptPubKey.addresses[0]
|
124 | if (!value || output.value < value) {
|
125 | value = output.value
|
126 | }
|
127 | if (sources.indexOf(destinationAddress) < 0) {
|
128 | tipAmount += output.value
|
129 | tipDestinationAddresses.push(destinationAddress)
|
130 | }
|
131 | }
|
132 | })
|
133 | tip.tipDestinationAddresses = tipDestinationAddresses
|
134 | tip.tipAmount = tipAmount
|
135 | tip.tipSourceAddresses = []
|
136 | tip.txid = tx.txid
|
137 | sources.forEach(function (source) {
|
138 | if (tipDestinationAddresses.indexOf(source) === -1) {
|
139 | tip.tipSourceAddresses.push(source)
|
140 | }
|
141 | })
|
142 | if (tip.tipDestinationAddresses.length === 0 && typeof (value) !== 'undefined') {
|
143 | tip.tipDestinationAddresses = [sources]
|
144 | tip.tipAmount = value
|
145 | }
|
146 | if (tip.openpublishSha1 && tip.tipDestinationAddresses && tip.tipAmount) {
|
147 | tips.push(tip)
|
148 | }
|
149 | })
|
150 | callback(false, tips)
|
151 | }
|
152 |
|
153 | var opentip = {
|
154 | create: create,
|
155 | scan: scan,
|
156 | scanSingle: scanSingle
|
157 | }
|
158 |
|
159 | module.exports = opentip
|