-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbitcoincli.go
More file actions
422 lines (352 loc) · 11.2 KB
/
bitcoincli.go
File metadata and controls
422 lines (352 loc) · 11.2 KB
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
package bitcoincli
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/pebbe/zmq4"
"io/ioutil"
"log"
"net/http"
"strconv"
)
// https://developer.bitcoin.org/reference/rpc/index.html
type BitcoinCli struct {
isTest bool
client *RpcClient
}
func NewBitcoinCli(config BitcoinCliConfig, walletTransactionListeners []func(trans RawTransaction)) *BitcoinCli {
rpcClient, err := newRpcClient(config.Host, config.Port, config.User, config.Password, config.UseSsl, config.Timeout)
if err != nil {
panic(err)
}
result := &BitcoinCli{isTest: config.IsTest, client: rpcClient}
if walletTransactionListeners != nil && len(walletTransactionListeners) > 0 {
if config.WalletNotifyConfig == nil {
log.Println("no WalletNotifyConfig")
} else {
result.startWalletTransactionsListener(config.WalletNotifyConfig.Host, config.WalletNotifyConfig.Port, walletTransactionListeners)
}
}
_, err = result.LoadAllWallets()
if err != nil {
panic(err)
}
return result
}
// Handle wallet transactions.
// You need to add at bitcoin.conf:
// "walletnotify=curl -d %s http://[YOUR_HOST_HERE]:[YOUR_PORT_HERE]"
func (b *BitcoinCli) startWalletTransactionsListener(host string, port int, listeners []func(trans RawTransaction)) error {
if len(listeners) == 0 {
return errors.New("no listeners")
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
txid := string(body)
rawTrans, err := b.GetRawTransaction(txid)
if err == nil {
for _, lis := range listeners {
lis(rawTrans)
}
} else {
log.Println("startWalletTransactionsListener", err)
}
})
go http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), nil)
return nil
}
func (b *BitcoinCli) IsTest() bool {
return b.isTest
}
func (b *BitcoinCli) Rpc() *RpcClient {
return b.client
}
// Handle all transactions.
// You need to enable zmq hashtx, for example at bitcoin.conf:
// "zmqpubhashtx=tcp://127.0.0.1:7334" (zmqpubhashtx=tcp://[YOUR_HOST_HERE]:[YOUR_PORT_HERE])
func (b *BitcoinCli) startAllTransactionsListener(host string, port int, listeners []func(trans RawTransaction)) {
go func () {
xsub, _ := zmq4.NewSocket(zmq4.SUB)
err := xsub.Connect("tcp://127.0.0.1:17334")
if err != nil {
panic(err)
}
err = xsub.SetSubscribe("hashtx")
if err != nil {
panic(err)
}
for {
msg, err := xsub.RecvMessageBytes(0)
if err != nil {
fmt.Println("transaction listener error: ", err)
continue
}
//msgType := string(msg[0])
msgBody := hex.EncodeToString(msg[1])
trans, err := b.GetRawTransaction(msgBody)
if err == nil {
for _, lis := range listeners {
lis(trans)
}
} else {
fmt.Println(msgBody)
}
}
}()
}
func (b *BitcoinCli) SendToAddress(walletName string, addr string, amount float64) (txid string, err error) {
r, err := b.client.CallWallet(walletName,"sendtoaddress", []interface{}{addr, amount})
if err = handleError(err, &r); err != nil {
return
}
err = json.Unmarshal(r.Result, &txid)
return
}
func (b *BitcoinCli) SendMany(walletName string, addrToAmountMap map[string]float64) (txid string, err error) {
r, err := b.client.CallWallet(walletName,"sendmany", []interface{}{"", addrToAmountMap})
if err = handleError(err, &r); err != nil {
return
}
err = json.Unmarshal(r.Result, &txid)
return
}
func (b *BitcoinCli) SendToAddressWithInfo(walletName string, addr string, amount float64, info string, blockchainInfo string) (txid string, err error) {
r, err := b.client.CallWallet(walletName,"sendtoaddress", []interface{}{addr, amount, info, blockchainInfo})
if err = handleError(err, &r); err != nil {
return
}
err = json.Unmarshal(r.Result, &txid)
return
}
// getbalance :
// Returns the total available balance.
func (b *BitcoinCli) GetBalance(walletName string, minConfirms int) (balance float64, err error) {
r, err := b.client.CallWallet(walletName,"getbalance", []interface{}{"*", minConfirms})
if err = handleError(err, &r); err != nil {
return
}
balance, err = strconv.ParseFloat(string(r.Result), 64)
return
}
func (b *BitcoinCli) CreateWallet(id string) (walletName WalletWithWarning, err error) {
r, err := b.client.Call("createwallet", []string{id})
if err = handleError(err, &r); err != nil {
return
}
err = json.Unmarshal(r.Result, &walletName)
return
}
// listwallets :
// Returns a list of currently loaded wallets.
func (b *BitcoinCli) ListWallets() (wallets []string, err error) {
r, err := b.client.Call("listwallets", []string{})
if err = handleError(err, &r); err != nil {
return
}
err = json.Unmarshal(r.Result, &wallets)
return
}
// listwallets :
// Returns a list of wallets in the wallet directory.
func (b *BitcoinCli) ListWalletDir() (wallets []string, err error) {
r, err := b.client.Call("listwalletdir", []string{})
if err = handleError(err, &r); err != nil {
return
}
var walletsList ListWalletDir
err = json.Unmarshal(r.Result, &walletsList)
for _, k := range walletsList.Wallets {
wallets = append(wallets, k.Name)
}
return
}
// loadwallet :
// Loads a wallet from a wallet file or directory.
func (b *BitcoinCli) LoadWallet(name string) (wallet WalletWithWarning, err error) {
r, err := b.client.Call("loadwallet", []string{name})
if err = handleError(err, &r); err != nil {
return
}
err = json.Unmarshal(r.Result, &wallet)
return
}
func (b *BitcoinCli) LoadAllWallets() (warnings []string, err error) {
allWallets, err := b.ListWalletDir()
if err != nil {
return
}
loadedWallets, err := b.ListWallets()
walletsToLoad := diffStringSlices(allWallets, loadedWallets)
fmt.Println(walletsToLoad)
if err != nil {
return
}
var wallet WalletWithWarning
for _, w := range walletsToLoad {
wallet, err = b.LoadWallet(w)
if err != nil {
return
}
if len(wallet.Warning) != 0 {
warnings = append(warnings, wallet.Warning)
}
}
return
}
func diffStringSlices(source, with []string) []string {
target := map[interface{}]bool{}
for _, x := range with{
target[x] = true
}
var result []string
for _, x := range source {
if _, ok := target[x]; !ok {
result = append(result, x)
}
}
return result
}
// unloadwallet :
// Unloads the wallet referenced by the request endpoint otherwise unloads the wallet specified in the argument.
func (b *BitcoinCli) UnloadWallet(name string) (wallet WalletWithWarning, err error) {
r, err := b.client.Call("unloadwallet", []string{name})
if err = handleError(err, &r); err != nil {
return
}
err = json.Unmarshal(r.Result, &wallet)
return
}
// gettransaction :
// Get detailed information about in-wallet transaction <txid>
func (b *BitcoinCli) GetTransaction(walletName string, txid string) (trans Transaction, err error) {
r, err := b.client.CallWallet(walletName,"gettransaction", []string{txid})
if err = handleError(err, &r); err != nil {
return
}
err = json.Unmarshal(r.Result, &trans)
return
}
// getrawtransaction :
// By default this function only works for mempool transactions. When called with a blockhash argument, getrawtransaction will return the transaction if the specified block is available and the transaction
//is found in that block. When called without a blockhash argument, getrawtransaction will return the transaction if it is in the mempool, or if -txindex is enabled and the transaction is in a block in the blockchain.
func (b *BitcoinCli) GetRawTransaction(txid string) (trans RawTransaction, err error) {
r, err := b.client.Call("getrawtransaction", []interface{}{txid, true})
if err = handleError(err, &r); err != nil {
return
}
err = json.Unmarshal(r.Result, &trans)
return
}
func (b *BitcoinCli) GetNewAddress(walletName string, label ...string) (addr string, err error) {
if len(label) > 1 {
err = errors.New("Bad parameters for GetNewAddress: you can set 0 or 1 label")
return
}
r, err := b.client.CallWallet(walletName,"getnewaddress", label)
if err = handleError(err, &r); err != nil {
return
}
err = json.Unmarshal(r.Result, &addr)
return
}
// GetWalletAddress Returns bitcoin address for receiving
// payments to this wallet.
func (b *BitcoinCli) GetWalletAddress(walletName string) (address AddressInfo, err error) {
r, err := b.ListPublicAddressesInfoByWallet(walletName)
if len(r) > 0 {
return r[0], err
}
return AddressInfo{}, err
}
// listaddressgroupings :
// Lists groups of addresses which have had their common ownership made public by common use as inputs or as the resulting change in past transactions
func (b *BitcoinCli) ListPublicAddressesInfoByWallet(walletName string) (result []AddressInfo, err error) {
r, err := b.client.CallWallet(walletName,"listaddressgroupings", []string{})
if err = handleError(err, &r); err != nil {
return
}
var addresses [][][]interface{}
err = json.Unmarshal(r.Result, &addresses)
if err == nil && len(addresses) > 0 {
for _, addrgroup := range addresses {
for _, v := range addrgroup {
wi := AddressInfo{
Address: v[0].(string),
Amount: v[1].(float64),
}
if len(v) > 2 {
wi.Label = v[2].(string)
}
result = append(result, wi)
}
}
}
return
}
// listaddressgroupings :
// Lists groups of addresses which have had their common ownership made public by common use as inputs or as the resulting change in past transactions
func (b *BitcoinCli) ListPublicAddressesMapByWallet(walletName string) (result map[string]float64, err error) {
r, err := b.client.CallWallet(walletName,"listaddressgroupings", []string{})
if err = handleError(err, &r); err != nil {
return
}
var addresses [][][]interface{}
err = json.Unmarshal(r.Result, &addresses)
result = make(map[string]float64)
if err == nil && len(addresses) > 0 {
for _, v := range addresses[0] {
result[v[0].(string)] = v[1].(float64)
}
}
return
}
// listlabels :
// LReturns the list of all labels, or labels that are assigned to addresses with a specific purpose.
func (b *BitcoinCli) ListLabels(walletName string) (labels []string, err error) {
r, err := b.client.CallWallet(walletName,"listlabels", []string{})
if err = handleError(err, &r); err != nil {
return
}
err = json.Unmarshal(r.Result, &labels)
return
}
// getaddressesbylabel :
// Returns the list of addresses assigned the specified label.
func (b *BitcoinCli) GetAddressesByLabel(walletName, label string) (addresses []string, err error) {
r, err := b.client.CallWallet(walletName,"getaddressesbylabel", []string{label})
if err = handleError(err, &r); err != nil {
return
}
var addressesMap map[string]interface{}
err = json.Unmarshal(r.Result, &addressesMap)
for addr, _ := range addressesMap {
addresses = append(addresses, addr)
}
return
}
func (b *BitcoinCli) GetBalanceAddress(walletName, address string) (balance float64, err error) {
list, err := b.ListPublicAddressesInfoByWallet(walletName)
if err != nil {
return
}
for _, w := range list {
if w.Address == address {
balance = w.Amount
return
}
}
err = errors.New("not found")
return
}
func (b *BitcoinCli) GetReceivedByAddress(walletName string, addr string, minConfirms int) (amount float64, err error) {
r, err := b.client.CallWallet(walletName,"getreceivedbyaddress", []interface{}{addr, minConfirms})
if err = handleError(err, &r); err != nil {
return
}
amount, err = strconv.ParseFloat(string(r.Result), 64)
return
}