pochette 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 32a5e22b912d750c86f5ce857e8fd6c13e0be105
4
- data.tar.gz: b4b5176cfaabc855f88c692d17f721699ff4dc68
3
+ metadata.gz: c505a9f75cdfc003b454e87cd97c48f8fc11aeb7
4
+ data.tar.gz: 9e6c94633014b5d0f1ab9328dd1d9f0edea20ca1
5
5
  SHA512:
6
- metadata.gz: a9e1050c5b93cf85eea51bbcfb3e16468a6331c5ff49b1e5d4ef9c9bdd9bfe1bc5a3fedcc0c98db244eec3121fe63a7de1a1ca6801fbe083935edf2f4fd0c7b1
7
- data.tar.gz: 9d806da72bafad0279da22e85e3375a36957a8d667cb994361cfb1071eee80cabd0d46d1740b6313b7ca4955529838372415c872b4ea3e5541f2e5cddad3e414
6
+ metadata.gz: ba821bb23b235bb6ca4b9a2249c6ca6a3844665bcfe1a967e16066da4976e37d1cf01ce7e7004cc83c522fc84359d0a419e14a352264c1a4a5aa570a8b3b55cf
7
+ data.tar.gz: 2b312820cb91d983c0638473d957a13b4f45fe99044aa4361eede24d61d1ec638b2da51fcc4243e1f8c9d13a2d96107e0f687e0a9a9a8ea6869e76ab348f3f3e
data/README.md CHANGED
@@ -8,14 +8,38 @@ bitcoin deposits to preparing transactions with bip32 paths instead of input add
8
8
  ready to be signed with a [Trezor Device](https://www.bitcointrezor.com/)
9
9
 
10
10
  Pochette offers a common interface to full bitcoin nodes like
11
- [Bitcoin Core](https://bitcoin.org/en/download) or [Toshi](http://toshi.io)
11
+ [Bitcoin Core](https://bitcoin.org/en/download)
12
+ [Blockchain.info](https://blockchain.info/api) or [Toshi](http://toshi.io)
12
13
  and will let you run several instances of each one of them simultaneously
13
- always choosing the most recent node to query.
14
+ always choosing the most recent node to query.
14
15
 
15
16
  It also provides a Pochette::TransactionBuilder class which receives a list
16
- of 'source' addresses and a list of recipients as "address/amount" pairs.
17
-
18
- ## Installation
17
+ of 'source' addresses and a list of recipients as "address/amount" pairs and
18
+ uses them to select unspent outputs and build a raw transaction to be signed
19
+ and broadcasted.
20
+
21
+ The Pochette::TrezorTransactionBuilder class extends Pochette::TransactionBuilder
22
+ including transactions, inputs and outputs that are formatted in a way they can
23
+ be passed directly to a Trezor device for signing.
24
+
25
+ ## Table of contents
26
+ - [Installation and Setup](#installation-and-setup)
27
+ - [Pochette::TransactionBuilder](#the-pochettetransactionbuilder)
28
+ - [Pochette::TrezorTransactionBuilder](#the-pochettetrezortransactionbuilder)
29
+ - [Backend API](#backend-api)
30
+ - [incoming_for(addresses, min_date)](#incoming_foraddresses-min_date)
31
+ - [balances_for(addresses, confirmations)](#balances_foraddresses-confirmations)
32
+ - [list_unspent(addresses)](#list_unspentaddresses)
33
+ - [list_transactions(txids)](#list_transactionstxids)
34
+ - [block_height](#block_height)
35
+ - [pushtx(hex)](#pushtxhex)
36
+ - Supported Backends
37
+ - [BitcoinCore Backend](#bitcoincore-backend)
38
+ - [BlockchainInfo Backend](#blockchaininfo-backend)
39
+ - [Toshi Backend](#toshi-backend)
40
+ - [Trendy Backend](#trendy-backend)
41
+
42
+ ## Installation and Setup
19
43
 
20
44
  Add this line to your application's Gemfile:
21
45
 
@@ -31,55 +55,522 @@ Or install it yourself as:
31
55
 
32
56
  $ gem install pochette
33
57
 
58
+ You will probably want to setup Pochette with a global default backend,
59
+ the backend can also be configured separately for each instance of
60
+ a Pochette::TransactionBuilder
61
+
62
+ ```ruby
63
+ >>> Pochette.backend = Pochette::Backends::BlockchainInfo.new
64
+ >>> Pochette::TransactionBuilder.backend = Pochette::Backends::BlockchainInfo.new
65
+ >>> Pochette::TrezorTransactionBuilder.backend = Pochette::Backends::BlockchainInfo.new
66
+ ```
67
+
68
+ Pochette can also be configured to use the bitcoin testnet, this will change
69
+ the default network used by the Bitcoin gem and may alter the way backends work too.
70
+
71
+ ```ruby
72
+ >>> Pochette.testnet = true
73
+ ```
74
+
34
75
  ## The Pochette::TransactionBuilder
35
76
 
36
77
  The TransactionBuilder builds transactions from a list of source addresses and a list of recipients,
37
- using Pochette.backend to fetch unspent outputs and related transaction data.
78
+ using a configured backend to fetch unspent outputs and related transaction data.
38
79
  Instantiating will perform all the required queries, you'll be left with a
39
80
  TransactionBuilder object that is either valid? or not, and if valid,
40
81
  you can query the results via to_hash.
41
82
 
83
+ #### Receives
42
84
  The TransactionBuilder's initializer receives a single options hash with:
43
- addresses:
44
- List of addresses in wallet.
45
- We will be spending their unspent outputs.
46
- outputs:
47
- List of pairs [recipient_address, amount]
48
- This will not be all the final outputs in the transaction,
49
- as a 'change' output may be added if needed.
50
- utxo_blacklist:
51
- List of utxos to ignore, a list of pairs [transaction hash, position]
52
- change_address:
53
- Change address to use. Will default to the first source address.
54
- fee_per_kb:
55
- Defaults to 10000 satoshis.
56
- spend_all:
57
- Wether to spend all available utxos or just select enough to
58
- cover the given outputs.
59
-
60
- TODO: Document as_hash output.
61
-
62
- ## Building for Trezor with Pochette::TrezorTransactionBuilder
63
-
64
- Same as TransactionBuilder but outputs a transaction hash with all the
65
- required data to create and sign a transaction using a BitcoinTrezor.
66
-
67
- * Uses BIP32 addresses instead of regular strings.
85
+
86
+ <dl>
87
+ <dt>addresses:</dt>
88
+ <dd>
89
+ List of addresses in wallet.
90
+ We will be spending their unspent outputs.
91
+ </dd>
92
+ <dt>outputs:</dt>
93
+ <dd>
94
+ List of pairs [recipient_address, amount]
95
+ This will not be all the final outputs in the transaction,
96
+ as a 'change' output may be adted if needed.
97
+ </dd>
98
+ <dt>utxo_blacklist:</dt>
99
+ <dd>
100
+ Optional. List of utxos to ignore, a list of pairs [transaction hash, position]
101
+ </dd>
102
+ <dt>change_address:</dt>
103
+ <dd>
104
+ Optional. Change address to use. Will default to the first source address.
105
+ </dd>
106
+ <dt>fee_per_kb:</dt>
107
+ <dd>
108
+ Optional. Defaults to 10000 satoshis.
109
+ </dd>
110
+ <dt>spend_all:</dt>
111
+ <dd>
112
+ Optional. Boolean. Wether to spend all available utxos or just select enough to
113
+ cover the given outputs.
114
+ </dd>
115
+ </dl>
116
+
117
+ #### Returns
118
+
119
+ A hash with
120
+
121
+ <dl>
122
+ <dt>input_total:</dt>
123
+ <dd>The sum of all input amounts, in satoshis.</dd>
124
+ <dt>output_total:</dt>
125
+ <dd>The sum of all outputs, in satoshis.</dd>
126
+ <dt>fee:</dt>
127
+ <dd>fee to pay (input_total - output_total).</dd>
128
+ <dt>outputs:</dt>
129
+ <dd>Array of [destination address, amount in satoshis]</dd>
130
+ <dt>inputs:</dt>
131
+ <dd>Array of [input address, utxo transaction hash, utxo position, amount]</dd>
132
+ <dt>utxos_to_blacklist:</dt>
133
+ <dd>
134
+ Transaction inputs formatted to be used as utxo_blacklist on another
135
+ TransactionBuilder.
136
+ </dd>
137
+ </dl>
138
+
139
+
140
+ ```ruby
141
+ >>> require 'pochette'
142
+ >>> backend = Pochette::Backends::BlockchainInfo.new
143
+ >>> transaction = Pochette::TransactionBuilder.new({
144
+ addresses: ["2NAHscN6XVqUPzBSJHC3fhkeF5SQVxiR9p9"],
145
+ outputs: [
146
+ ["mvtUvWSWCU7knrcMVzjcKJgjL1LdekLK5q", 1_0000_0000],
147
+ ],
148
+ utxo_blacklist: [
149
+ ["0ded7f014fa3213e9b000bc81b8151bc6f2f926b9afea6e3643c8ad658353c72", 1]
150
+ ],
151
+ change_address: 'mgaTEUM4ZE9kLiK58FcffwHfvxpte5CfvE',
152
+ fee_per_kb: 10000,
153
+ spend_all: false,
154
+ backend: backend
155
+ })
156
+ >>> transaction.valid?
157
+ => true
158
+ >>> transaction.as_hash
159
+ => {
160
+ input_total: 2_0000_0000,
161
+ output_total: 1_9999_0000,
162
+ fee: 10000,
163
+ outputs: [
164
+ ["mvtUvWSWCU7knrcMVzjcKJgjL1LdekLK5q", 1_0000_0000],
165
+ ["mgaTEUM4ZE9kLiK58FcffwHfvxpte5CfvE", 9999_0000],
166
+ ],
167
+ inputs: [
168
+ [ "2NAHscN6XVqUPzBSJHC3fhkeF5SQVxiR9p9",
169
+ "956b30c3c4335f019dbee60c60d76994319473acac356f774c7858cd5c968e40",
170
+ 1,
171
+ 200000000
172
+ ],
173
+ ],
174
+ utxos_to_blacklist: [
175
+ ["956b30c3c4335f019dbee60c60d76994319473acac356f774c7858cd5c968e40", 1]
176
+ ],
177
+ }
178
+ ```
179
+
180
+ ## The Pochette::TrezorTransactionBuilder
181
+
182
+ Builds a transaction like TransactionBuilder but includes transaction data
183
+ and formats inputs and outputs in a way that can be sent directly to your trezor
184
+ device.
185
+ If you're using [Trezor Connect](https://github.com/trezor/connect) for signing
186
+ then you won't need to pass in the transactions.
187
+
188
+ #### Receives
189
+ The TransactionBuilder's initializer receives a single options hash with:
190
+
191
+ <dl>
192
+ <dt>addresses:</dt>
193
+ <dd>
194
+ List of addresses in wallet. We will be spending their unspent outputs.
68
195
  Each address is represented as a pair, with the public address string
69
196
  and the BIP32 path as a list of integers, for example:
70
197
  ['public-address-as-string', [44, 1, 3, 11]]
71
-
72
- * Includes associated transaction data for each input being spent,
73
- ready to be consumed by your Trezor device.
74
-
75
- * Outputs are represented as JSON with script_type as expected by Trezor.
198
+ </dd>
199
+ <dt>outputs:</dt>
200
+ <dd>
201
+ List of pairs [recipient_address, amount]
202
+ This will not be all the final outputs in the transaction,
203
+ as a 'change' output may be adted if needed.
204
+ </dd>
205
+ <dt>utxo_blacklist:</dt>
206
+ <dd>
207
+ Optional. List of utxos to ignore, a list of pairs [transaction hash, position]
208
+ </dd>
209
+ <dt>change_address:</dt>
210
+ <dd>
211
+ Optional. Change address to use. Will default to the first source address.
212
+ </dd>
213
+ <dt>fee_per_kb:</dt>
214
+ <dd>
215
+ Optional. Defaults to 10000 satoshis.
216
+ </dd>
217
+ <dt>spend_all:</dt>
218
+ <dd>
219
+ Optional. Boolean. Wether to spend all available utxos or just select enough to
220
+ cover the given outputs.
221
+ </dd>
222
+ </dl>
223
+
224
+ #### Returns
225
+
226
+ A hash with
227
+
228
+ <dl>
229
+ <dt>input_total:</dt>
230
+ <dd>The sum of all input amounts, in satoshis.</dd>
231
+ <dt>output_total:</dt>
232
+ <dd>The sum of all outputs, in satoshis.</dd>
233
+ <dt>fee:</dt>
234
+ <dd>fee to pay (input_total - output_total).</dd>
235
+ <dt>outputs:</dt>
236
+ <dd>Array of [destination address, amount in satoshis]</dd>
237
+ <dt>inputs:</dt>
238
+ <dd>Array of [input address, utxo transaction hash, utxo position, amount]</dd>
239
+ <dt>utxos_to_blacklist:</dt>
240
+ <dd>
241
+ Transaction inputs formatted to be used as utxo_blacklist on another
242
+ TransactionBuilder.
243
+ </dd>
244
+ <dt>transactions:</dt>
245
+ <dd>Transaction data for each input.</dd>
246
+ <dt>trezor_inputs:</dt>
247
+ <dd>
248
+ List of inputs as hashes with bip32 paths instead of addresses
249
+ { address_n: [42,1,1],
250
+ prev_hash: "956b30c3c4335f019dbee60c60d76994319473acac356f774c7858cd5c968e40",
251
+ prev_index: 1}
252
+ </dd>
253
+ <dt>trezor_outputs:</dt>
254
+ <dd>
255
+ List of outputs as Hashes with:
76
256
  { script_type: 'PAYTOADDRESS',
77
257
  address: '1address-as-string',
78
- amount: amount_in_satoshis }
258
+ amount: amount in satoshis }
259
+ </dd>
260
+ </dl>
79
261
 
80
- TODO: Document as_hash output.
262
+ ```ruby
263
+ >>> require 'pochette'
264
+ >>> backend = Pochette::Backends::BlockchainInfo.new
265
+ >>> transaction = Pochette::TransactionBuilder.new({
266
+ addresses: [
267
+ ["2NAHscN6XVqUPzBSJHC3fhkeF5SQVxiR9p9", [44, 1, 3, 11]]
268
+ ],
269
+ outputs: [
270
+ ["mvtUvWSWCU7knrcMVzjcKJgjL1LdekLK5q", 1_0000_0000],
271
+ ],
272
+ utxo_blacklist: [
273
+ ["0ded7f014fa3213e9b000bc81b8151bc6f2f926b9afea6e3643c8ad658353c72", 1]
274
+ ],
275
+ change_address: 'mgaTEUM4ZE9kLiK58FcffwHfvxpte5CfvE',
276
+ fee_per_kb: 10000,
277
+ spend_all: false
278
+ })
279
+ >>> transaction.valid?
280
+ => true
281
+ >>> transaction.as_hash
282
+ => {
283
+ input_total: 2_0000_0000,
284
+ output_total: 1_9999_0000,
285
+ fee: 10000,
286
+ outputs: [
287
+ ["mvtUvWSWCU7knrcMVzjcKJgjL1LdekLK5q", 1_0000_0000],
288
+ ["mgaTEUM4ZE9kLiK58FcffwHfvxpte5CfvE", 9999_0000],
289
+ ],
290
+ trezor_outputs: [
291
+ { script_type: 'PAYTOADDRESS',
292
+ address: "mvtUvWSWCU7knrcMVzjcKJgjL1LdekLK5q",
293
+ amount: 1_0000_0000 },
294
+ { script_type: 'PAYTOADDRESS',
295
+ address: "mgaTEUM4ZE9kLiK58FcffwHfvxpte5CfvE",
296
+ amount: 9999_0000 },
297
+ ],
298
+ inputs: [
299
+ [ "2NAHscN6XVqUPzBSJHC3fhkeF5SQVxiR9p9",
300
+ "956b30c3c4335f019dbee60c60d76994319473acac356f774c7858cd5c968e40",
301
+ 1,
302
+ 200000000
303
+ ],
304
+ ],
305
+ trezor_inputs: [
306
+ { address_n: [43,1,3,11],
307
+ prev_hash: "956b30c3c4335f019dbee60c60d76994319473acac356f774c7858cd5c968e40",
308
+ prev_index: 1 },
309
+ ],
310
+ transactions: [
311
+ { hash: "956b30c3c4335f019dbee60c60d76994319473acac356f774c7858cd5c968e40",
312
+ version: "1",
313
+ lock_time: "0",
314
+ inputs: [
315
+ { prev_hash: "158d6bbe586b4e00347f992e8296532d69f902d0ead32d964b6c87d4f8f0d3ea",
316
+ prev_index: 0,
317
+ sequence: 4294967295,
318
+ script_sig: "SCRIPTSCRIPTSCRIPT"
319
+ }
320
+ ],
321
+ bin_outputs: [
322
+ { amount: 1234568, script_pubkey: "76a914988cb8253f4e28be6e8bfded1b4aa11c646e1a8588ac" },
323
+ { amount: 200000000, script_pubkey: "76a914988cb8253f4e28be6e8bfded1b4aa11c646e1a8588ac"}
324
+ ]
325
+ }
326
+ ],
327
+ utxos_to_blacklist: [
328
+ ["956b30c3c4335f019dbee60c60d76994319473acac356f774c7858cd5c968e40", 1]
329
+ ],
330
+ }
331
+ ```
332
+
333
+ ## Backend API
334
+
335
+ Pochette offers a common interface to full bitcoin nodes like
336
+ [Bitcoin Core](https://bitcoin.org/en/download)
337
+ [Blockchain.info](https://blockchain.info/api) or [Toshi](http://toshi.io)
338
+ and will let you run several instances of each one of them simultaneously
339
+ always choosing the most recent node to query.
340
+
341
+ ## incoming_for(addresses, min_date)
342
+
343
+ The incoming_for method is useful when registering deposits received to
344
+ a number of bitcoin addresses.
345
+
346
+ #### Receives
347
+ - addresses: A list of public bitcoin addresses to check for incoming transactions.
348
+ - min_date: Do not check for deposits earlier than this date. This is only to prevent
349
+ fetching too many results if the backend was to return too many,
350
+ each backend may apply its own limits so higher value here is not guaranteed to
351
+ fetch more results.
352
+
353
+ #### Returns
354
+ A list with
355
+
356
+ - Amount received, in satoshis.
357
+ - Address which received the deposit.
358
+ - Transaction hash for the deposit.
359
+ - Confirmations for the transaction.
360
+ - Position of this deposit in the transaction outputs list.
361
+ - Senders, as a comma-separated list of addresses (no whitespaces)
362
+
363
+ ```ruby
364
+ >>> require 'pochette'
365
+ >>> backend = Pochette::Backends::BlockchainInfo.new
366
+ >>> addresses = [
367
+ 'mjfa56Keq7PXRKgdPSDB6eWLp4aaAVcj6L',
368
+ 'mwZE4QfzzriE7nsgHSWbgmtT7s6SDysYvP',
369
+ 'mvrDG7Ts6Mq9ejhZxdsQLjbScycVaktqsg',
370
+ 'mxYzRdJfPk8PcaKSsSzNkX85mMfNcr2CGr',
371
+ 'mzbXim4u1Nq4J2kVggu471pZL3ahxNkmE9',
372
+ 'mhLAgRz5f1YogfYBZCDFSRt3ceeKBPVEKg',
373
+ ]
374
+ >>> Pochette.backend.incoming_for(addresses, 1.day.ago)
375
+ => [
376
+ [ 500000, "mjfa56Keq7PXRKgdPSDB6eWLp4aaAVcj6L",
377
+ "fb401691795a73e0160252c00af18327a15006fcdf877ccca0c116809669032e", 1629, 0,
378
+ "my2hmDuD9XjmtQWFu9HyyNAsE5WGSDBKpQ"],
379
+ [100000, "mxYzRdJfPk8PcaKSsSzNkX85mMfNcr2CGr",
380
+ "250978b77fe1310d6c72239d9e9589d7ac3dc6edf1b2412806ace5104553da34", 1648, 1,
381
+ "mv9ES7SmQQQ8dpMravBKsLWukgxU2DXfFs"],
382
+ [500000, "mvrDG7Ts6Mq9ejhZxdsQLjbScycVaktqsg",
383
+ "d9afd460b0a5e065fdd87bf97cb1843a29ea588c59daabd1609794e8166bb75f", 1648, 0,
384
+ "my2hmDuD9XjmtQWFu9HyyNAsE5WGSDBKpQ"],
385
+ [ 100000, "mxYzRdJfPk8PcaKSsSzNkX85mMfNcr2CGr",
386
+ "5bd72a4aa7818f47ac8943e3e17519be00c46530760860e608d898d728b9d46e", 553, 1,
387
+ "mvUhgW1ZcUju181bvwEhmZu2x2sRdbV4y2"],
388
+ [ 500000, "mwZE4QfzzriE7nsgHSWbgmtT7s6SDysYvP",
389
+ "b252037526ecb616ab5901552abb903f00bf73400a1fc49b5b5bd699b84bce77", 1632, 0,
390
+ "my2hmDuD9XjmtQWFu9HyyNAsE5WGSDBKpQ"],
391
+ [ 500000, "mzbXim4u1Nq4J2kVggu471pZL3ahxNkmE9",
392
+ "ff768084764a05d1de72628432c0a4419538b2786089ec8ad009f6096bc69fe1", 1660, 0,
393
+ "my2hmDuD9XjmtQWFu9HyyNAsE5WGSDBKpQ"]
394
+ ]
395
+ ```
396
+
397
+ ## balances_for(addresses, confirmations)
398
+
399
+ Gets confirmed and unconfirmed sent, received and total balances for the given
400
+ addresses. It's useful for payment processing as you can see what's the total
401
+ amount seen on the network for a given address, and the final confirmed amount as well.
402
+
403
+ #### Receives
404
+ - addresses: A list of public bitcoin addresses to check balances for.
405
+ - confirmations: How many confirmations to use for the 'confirmed' amounts.
406
+
407
+ #### Returns
408
+ A hash where keys are public addresses and values ara a list of
409
+
410
+ - Confirmed received
411
+ - Confirmed sent
412
+ - Confirmed balance
413
+ - Unconfirmed received
414
+ - Unconfirmed sent
415
+ - Unconfirmed balance
416
+
417
+ ```ruby
418
+ >>> require 'pochette'
419
+ >>> backend = Pochette::Backends::BlockchainInfo.new
420
+ >>> addresses = [
421
+ 'mjfa56Keq7PXRKgdPSDB6eWLp4aaAVcj6L',
422
+ 'mwZE4QfzzriE7nsgHSWbgmtT7s6SDysYvP',
423
+ 'mvrDG7Ts6Mq9ejhZxdsQLjbScycVaktqsg',
424
+ 'mxYzRdJfPk8PcaKSsSzNkX85mMfNcr2CGr',
425
+ 'mzbXim4u1Nq4J2kVggu471pZL3ahxNkmE9',
426
+ 'mhLAgRz5f1YogfYBZCDFSRt3ceeKBPVEKg',
427
+ ]
428
+ >>> backend.balances_for(addresses, 6)
429
+ >>> [
430
+ "mhLAgRz5f1YogfYBZCDFSRt3ceeKBPVEKg" =>
431
+ [0.00544426, 0.00544426, 0.0, 0.00544426, 0.00544426, 0.0],
432
+ "mjfa56Keq7PXRKgdPSDB6eWLp4aaAVcj6L" =>
433
+ [0.005, 0.0, 0.005, 0.006, 0.0, 0.006],
434
+ "mvrDG7Ts6Mq9ejhZxdsQLjbScycVaktqsg" =>
435
+ [0.005, 0.0, 0.005, 0.005, 0.0, 0.005],
436
+ "mwZE4QfzzriE7nsgHSWbgmtT7s6SDysYvP" =>
437
+ [0.005, 0.0, 0.005, 0.005, 0.0, 0.005],
438
+ "mxYzRdJfPk8PcaKSsSzNkX85mMfNcr2CGr" =>
439
+ [0.002, 0.0, 0.002, 0.002, 0.0, 0.002],
440
+ "mzbXim4u1Nq4J2kVggu471pZL3ahxNkmE9" =>
441
+ [0.005, 0.0, 0.005, 0.005, 0.0, 0.005],
442
+ ```
81
443
 
82
- ## Using a Bitcoin-Core backend.
444
+ ## list_unspent(addresses)
445
+
446
+ Gets unspent transaction outputs (a.k.a. utxos) for all the given addresses.
447
+ You may not need to use this directly, but rather through a
448
+ Pochette::TransactionBuilder which is smart about selecting utxos.
449
+
450
+ #### Receives
451
+ - addresses: A list of public bitcoin addresses to check balances for.
452
+
453
+ #### Returns
454
+ A list of list, each of them is
455
+
456
+ - Address
457
+ - Transaction Hash
458
+ - Output position in transaction
459
+ - Unspent amount
460
+
461
+ ```ruby
462
+ >>> require 'pochette'
463
+ >>> backend = Pochette::Backends::BlockchainInfo.new
464
+ >>> addresses = [
465
+ 'mjfa56Keq7PXRKgdPSDB6eWLp4aaAVcj6L',
466
+ 'mwZE4QfzzriE7nsgHSWbgmtT7s6SDysYvP',
467
+ 'mvrDG7Ts6Mq9ejhZxdsQLjbScycVaktqsg',
468
+ 'mxYzRdJfPk8PcaKSsSzNkX85mMfNcr2CGr',
469
+ 'mzbXim4u1Nq4J2kVggu471pZL3ahxNkmE9',
470
+ 'mhLAgRz5f1YogfYBZCDFSRt3ceeKBPVEKg',
471
+ ]
472
+ >>> backend.list_unspent(addresses).should == [
473
+ ["mjfa56Keq7PXRKgdPSDB6eWLp4aaAVcj6L",
474
+ "fb401691795a73e0160252c00af18327a15006fcdf877ccca0c116809669032e", 0, 500000],
475
+ ["mxYzRdJfPk8PcaKSsSzNkX85mMfNcr2CGr",
476
+ "250978b77fe1310d6c72239d9e9589d7ac3dc6edf1b2412806ace5104553da34", 1, 100000],
477
+ ["mvrDG7Ts6Mq9ejhZxdsQLjbScycVaktqsg",
478
+ "d9afd460b0a5e065fdd87bf97cb1843a29ea588c59daabd1609794e8166bb75f", 0, 500000],
479
+ ["mxYzRdJfPk8PcaKSsSzNkX85mMfNcr2CGr",
480
+ "5bd72a4aa7818f47ac8943e3e17519be00c46530760860e608d898d728b9d46e", 1, 100000],
481
+ ["mwZE4QfzzriE7nsgHSWbgmtT7s6SDysYvP",
482
+ "b252037526ecb616ab5901552abb903f00bf73400a1fc49b5b5bd699b84bce77", 0, 500000],
483
+ ["mjfa56Keq7PXRKgdPSDB6eWLp4aaAVcj6L",
484
+ "9142d7a8e96124a36db9708dd21afa4ac81f15a77bd85c06f16e808a4d700da2", 1, 100000],
485
+ ["mzbXim4u1Nq4J2kVggu471pZL3ahxNkmE9",
486
+ "ff768084764a05d1de72628432c0a4419538b2786089ec8ad009f6096bc69fe1", 0, 500000]
487
+ ]
488
+ ```
489
+
490
+ ## list_transactions(txids)
491
+
492
+ List full transaction data for the given transaction hashes. The output
493
+ is formatted for Trezor as required by their multi-step signature process.
494
+ You may not want to use this directly and use Pochette::TrezorTransactionBuilder
495
+ instead.
496
+
497
+ #### Receives
498
+ - Transactions: A list of Transaction ids
499
+
500
+ #### Returns
501
+ A list of ruby hashes will transaction data for each id passed.
502
+
503
+ ```ruby
504
+ >>> require 'pochette'
505
+ >>> backend = Pochette::Backends::BlockchainInfo.new
506
+ >>> transactions = [
507
+ "fb401691795a73e0160252c00af18327a15006fcdf877ccca0c116809669032e",
508
+ "250978b77fe1310d6c72239d9e9589d7ac3dc6edf1b2412806ace5104553da34",
509
+ ]
510
+ >>> backend.list_transactions(transactions)
511
+ => [{ hash: "fb401691795a73e0160252c00af18327a15006fcdf877ccca0c116809669032e",
512
+ version: 1,
513
+ lock_time: 0,
514
+ inputs: [
515
+ { prev_hash: "f948600a52719ec63947bd47105411c2cb032bda91b08b25bb7b1de22f1f7458",
516
+ prev_index: 1,
517
+ sequence: 4294967295,
518
+ script_sig: "483045022100898b773c1cf095d5c1608a892673ad067387663e00327e7b88dd48cc4eb9cf2a02203a53336afd4440ea52c6a55e07ac60035dc386cb01b5b6810efac018f0346fad01210316cff587a01a2736d5e12e53551b18d73780b83c3bfb4fcf209c869b11b6415e"
519
+ }
520
+ ],
521
+ bin_outputs: [
522
+ { amount: 500000, script_pubkey: "76a9142d81b210deb7e22475cd7f2fda0bf582dddc9da788ac" },
523
+ { amount: 276607256, script_pubkey: "76a914c01a7ca16b47be50cbdbc60724f701d52d75156688ac"}
524
+ ]
525
+ },
526
+ { hash: "250978b77fe1310d6c72239d9e9589d7ac3dc6edf1b2412806ace5104553da34",
527
+ version: 1,
528
+ lock_time: 0,
529
+ inputs: [
530
+ { prev_hash: "8505c663d0414b678827eed85ba9e7652e616c19ff1be871f6b083d5ed400a20",
531
+ prev_index: 0,
532
+ sequence: 4294967295,
533
+ script_sig: "47304402203ac7cb9afe1a14189b63807aff301ef6bb8507f6dc0471bcee5362282a8c3e38022012c3bfe9680f013735f0d6d012d6420383c0e3a1dbf010f97be9cc834a93d1f601210221af8672ff613d2ea198d8dadcc387a36ef47d7cba6f541221db61b96aa20149"
534
+ }
535
+ ],
536
+ bin_outputs: [
537
+ { amount: 999293000, script_pubkey: "76a91426beab63a5fb7b2103929e91b65f339a2c5b285088ac"},
538
+ { amount: 100000, script_pubkey: "76a914badcbfae4d83a52dc2c8f68605663adc9d4922a688ac"}
539
+ ]
540
+ }
541
+ ]
542
+ ```
543
+
544
+ ## block_height
545
+
546
+ Get the latest block height for this backend. Always in the main branch.
547
+
548
+ ```ruby
549
+ >>> require 'pochette'
550
+ >>> backend = Pochette::Backends::BlockchainInfo.new
551
+ >>> backend.get_height
552
+ => 376152
553
+ ```
554
+
555
+ ## pushtx
556
+
557
+ Propagates a raw transaction to the network.
558
+
559
+ #### Receives
560
+ - transaction: A raw transaction in hex format
561
+
562
+ #### Returns
563
+ The transaction id (hash)
564
+
565
+ ```ruby
566
+ >>> require 'pochette'
567
+ >>> hex = "0100000001d11a6cc978fc41aaf5b24fc5c8ddde71fb91ffdba9579cd62ba20fc284b2446c000000008a47304402206d2f98829a9e5017ade2c084a8b821625c35aeaa633f718b1c348906afbe68b00220094cb8ee519adcebe866e655532abab818aa921144bd98a12491481931d2383a014104e318459c24b89c0928cec3c9c7842ae749dcca78673149179af9155c80f3763642989df3ffe34ab205d02e2efd07e9a34db2f00ed8b821dd5bb087ff64df2c9effffffff0280f0fa02000000001976a9149b754a70b9a3dbb64f65db01d164ef51101c18d788ac40aeeb02000000001976a914aadf5d54eda13070d39af72eb5ce40b1d3b8282588ac00000000"
568
+ >>> backend = Pochette::Backends::BlockchainInfo.new
569
+ >>> backend.pushtx(hex)
570
+ => 'fb92420f73af6d25f5fab93435bc6b8ebfff3a07c02abd053f0923ae296fe380'
571
+ ```
572
+
573
+ ## BitcoinCore backend
83
574
 
84
575
  Pochette will connect to your bitcoin-core node via JSON-RPC, using the
85
576
  [bitcoin-rpc gem](https://github.com/bitex-la/bitcoin-rpc)
@@ -91,13 +582,41 @@ To properly use Pochette you need to be running your bitcoin node with setting t
91
582
  Also, if you're creating new addresses and want bitcoin-core to track them you'll want to import
92
583
  them using the bitcoin-rpc gem, like so:
93
584
 
94
- >>> BitcoinRpc::Client.new('http://user:pass@your_server').importaddress('1PUBLICADDRESS', '', false)
585
+ ```ruby
586
+ >>> BitcoinRpc::Client.new('http://user:pass@your_server').importaddress('1PUBLICADDRESS', '', false)
587
+ ```
95
588
 
96
589
  Setting up bitcoin-core as a backend can be done like this:
97
590
 
98
- >>> Pochette.backend = Pochette::Backends::BitcoinCore.new('http://user:pass@your_server')
591
+ ```ruby
592
+ >>> Pochette.backend = Pochette::Backends::BitcoinCore.new('http://user:pass@your_server')
593
+ ```
594
+
595
+ ## BlockchainInfo backend
596
+
597
+ Pochette can use blockchain.info's public API to fetch unspent outputs,
598
+ sleeping a bit after each request to prevent blockchain.info from banning your IP.
599
+ This backend is probably the slowest one but also the one that's more convenient for
600
+ testing and managing small wallets.
601
+
602
+ This backend is not usable for testnet transactions, all queries will be done to the
603
+ main network.
99
604
 
100
- ## Using a Toshi backend.
605
+ You can create a blockchain.info backend like this
606
+
607
+ ```ruby
608
+ >>> Pochette::Backends::BlockchainInfo.new
609
+ ```
610
+
611
+ The default cooldown time is 1 second after each request, but if you have a Blockchain.info
612
+ API key you can configure your backend like so:
613
+
614
+ ```ruby
615
+ >>> Pochette::Backends::BlockchainInfo.cooldown = 0.1 # Make the cooldown a tenth of a second
616
+ >>> Pochette.backend = Pochette::Backends::BlockchainInfo.new("your_api_key")
617
+ ```
618
+
619
+ ## Toshi backend
101
620
 
102
621
  Pochette will connect to your Toshi node's postgres database directly.
103
622
  It's provided as a separate gem as it depends on the pg gem which needs local
@@ -105,7 +624,7 @@ postgres extensions to work. You may need to add some extra indexes to your post
105
624
  to speed things up when using Pochette.
106
625
  [See the gem readme](https://github.com/bitex-la/pochette-toshi) for more info.
107
626
 
108
- ## Using the best of many available backends
627
+ ## Trendy Backend
109
628
 
110
629
  Pochette provides a higher level Backend Pochette::Backends::Trendy which chooses
111
630
  between a pool of available backends always using the one at the highest block height,
@@ -114,9 +633,11 @@ between a pool of available backends always using the one at the highest block h
114
633
  This is useful for automatic fallbacks and redundancy, you could also mix Toshi and Bitcoin-Core
115
634
  backends and use whatever looks more up to date.
116
635
 
117
- >>> alpha = Pochette::Backends::BitcoinCore.new('http://user:pass@alpha_host')
118
- >>> beta = Pochette::Backends::BitcoinCore.new('http://user:pass@beta_host')
119
- >>> Pochette.backend = Pochette::Backends::Trendy.new([alpha, beta])
636
+ ```ruby
637
+ >>> alpha = Pochette::Backends::BitcoinCore.new('http://user:pass@alpha_host')
638
+ >>> beta = Pochette::Backends::BitcoinCore.new('http://user:pass@beta_host')
639
+ >>> Pochette.backend = Pochette::Backends::Trendy.new([alpha, beta])
640
+ ```
120
641
 
121
642
  ## Development
122
643
 
@@ -6,12 +6,24 @@ require "bitcoin"
6
6
 
7
7
  module Pochette
8
8
  mattr_accessor :backend
9
+
10
+ def self.testnet=(v)
11
+ @testnet = v
12
+ Bitcoin.network = v ? :testnet : :bitcoin
13
+ end
14
+ def self.testnet
15
+ @testnet
16
+ end
17
+ def self.testnet?
18
+ self.testnet
19
+ end
9
20
 
10
21
  module Backends
11
22
  end
12
23
  end
13
24
 
14
25
  require "pochette/backends/bitcoin_core"
26
+ require "pochette/backends/blockchain_info"
15
27
  require "pochette/backends/trendy"
16
28
  require "pochette/transaction_builder"
17
29
  require "pochette/trezor_transaction_builder"
@@ -102,4 +102,8 @@ class Pochette::Backends::BitcoinCore
102
102
  def block_height
103
103
  client.getinfo[:blocks]
104
104
  end
105
+
106
+ def pushtx(hex)
107
+ client.sendrawtransaction(hex)
108
+ end
105
109
  end
@@ -0,0 +1,111 @@
1
+ # A bitcoin backend that uses blockchain.info to retrieve information.
2
+ # See Pochette::Backends::Trendy to learn more about the backend
3
+ # interface and contract.
4
+ require 'open-uri'
5
+
6
+ class Pochette::Backends::BlockchainInfo
7
+ cattr_accessor(:cooldown){1}
8
+ attr_accessor :api_key
9
+
10
+ def initialize(key = nil)
11
+ self.api_key = key
12
+ end
13
+
14
+ def list_unspent(addresses)
15
+ json = get_json("unspent", {active: addresses.join('|'), format: 'json'})
16
+ json['unspent_outputs'].collect do |utxo|
17
+ address = Bitcoin::Script.new(utxo['script'].htb).get_address
18
+ [address, utxo['tx_hash_big_endian'], utxo['tx_output_n'].to_i, utxo['value']]
19
+ end
20
+ end
21
+
22
+ def balances_for(addresses, confirmations)
23
+ json = get_json("multiaddr", {active: addresses.join('|'), format: 'json'})
24
+ result = {}
25
+ json['addresses'].collect do |a|
26
+ address = a['address']
27
+ sent = get_json("q/getsentbyaddress/#{address}",
28
+ {confirmations: confirmations, format: 'json'})
29
+ received = get_json("q/getreceivedbyaddress/#{address}",
30
+ {confirmations: confirmations, format: 'json'})
31
+ result[address] = [
32
+ sat(received), sat(sent), sat(received - sent),
33
+ sat(a['total_received']), sat(a['total_sent']), sat(a['final_balance'])]
34
+ end
35
+ result
36
+ end
37
+
38
+ def sat(x)
39
+ x.to_d / 1_0000_0000
40
+ end
41
+
42
+ def incoming_for(addresses, min_date)
43
+ return unless latest_block = block_height
44
+ addresses.in_groups_of(50, false).collect do |group|
45
+ incoming_for_helper(group, latest_block)
46
+ end.flatten(1)
47
+ end
48
+
49
+ def incoming_for_helper(addresses, latest_block)
50
+ json = get_json("multiaddr", {active: addresses.join('|'), format: 'json'})
51
+
52
+ json['txs'].collect do |transaction|
53
+ transaction['out'].collect do |out|
54
+ next unless addresses.include? out['addr']
55
+ confirmations = latest_block - transaction['block_height'].to_i
56
+ senders = transaction['inputs'].collect{ |i| i['prev_out']['addr'] }.join(',')
57
+ [ out['value'].to_d, out['addr'], transaction['hash'], confirmations, out['n'], senders ]
58
+ end.compact
59
+ end.flatten(1)
60
+ end
61
+
62
+ def list_transactions(txids)
63
+ return nil if txids.empty?
64
+ txids.collect do |txid|
65
+ tx = get_json("rawtx/#{txid}", {format: 'json'})
66
+ inputs = tx['inputs'].collect do |i|
67
+ prevhash = get_json("rawtx/#{i['prev_out']['tx_index']}", {format: 'json'})['hash']
68
+ { prev_hash: prevhash,
69
+ prev_index: i['prev_out']['n'],
70
+ sequence: i['sequence'],
71
+ script_sig: i['script']
72
+ }
73
+ end
74
+
75
+ outputs = tx['out'].collect do |o|
76
+ { amount: o['value'].to_i, script_pubkey: o['script'] }
77
+ end
78
+
79
+ { hash: tx['hash'], version: tx['ver'], lock_time: tx['lock_time'],
80
+ inputs: inputs, bin_outputs: outputs}
81
+ end
82
+ end
83
+
84
+ def block_height
85
+ get_json("latestblock", {format: 'json'})['height'].to_i
86
+ end
87
+
88
+ def pushtx(hex)
89
+ uri = URI.parse("https://blockchain.info/pushtx")
90
+ params = { "tx" => hex }
91
+ params['api_code'] = api_key if api_key
92
+ Net::HTTP.post_form(uri, params)
93
+ Bitcoin::Protocol::Tx.new(hex.htb).hash
94
+ end
95
+
96
+ def get_json(path, params={})
97
+ params['api_code'] = api_key if api_key
98
+ query = params.empty? ? '' : "?#{params.to_query}"
99
+ retries = 30
100
+ begin
101
+ raw_response = open("https://blockchain.info/#{path}#{query}").read
102
+ sleep cooldown
103
+ Oj.load(raw_response)
104
+ rescue OpenURI::HTTPError => e
105
+ raise if retries < 0 || e.message.to_i != 429
106
+ retries -= 1
107
+ sleep (cooldown * 5)
108
+ retry
109
+ end
110
+ end
111
+ end
@@ -42,6 +42,13 @@ class Pochette::Backends::Trendy
42
42
  # [[address, txid, position (vout), amount (in satoshis)], ...]
43
43
  def list_unspent(addresses)
44
44
  backend.list_unspent(addresses)
45
+ rescue OpenURI::HTTPError => e
46
+ # Blockchain.info returns 500 when there are no unspent outputs
47
+ if e.io.read == "No free outputs to spend"
48
+ return []
49
+ else
50
+ raise
51
+ end
45
52
  end
46
53
 
47
54
  # Gets information for the given transactions
@@ -3,25 +3,13 @@
3
3
  # Instantiating will perform all the given queries, you'll be left with a
4
4
  # TransactionBuilder object that is either valid? or not, and if valid
5
5
  # you can query the results via to_hash.
6
- # Options:
7
- # addresses:
8
- # List of addresses in wallet.
9
- # We will be spending their unspent outputs.
10
- # outputs:
11
- # List of pairs [recipient_address, amount]
12
- # This will not be all the final outputs in the transaction,
13
- # as a 'change' output may be added if needed.
14
- # utxo_blacklist:
15
- # List of utxos to ignore, a list of pairs [transaction hash, position]
16
- # change_address:
17
- # Change address to use. Will default to the first source address.
18
- # fee_per_kb:
19
- # Defaults to 10000 satoshis.
20
- # spend_all:
21
- # Wether to spend all available utxos or just select enough to
22
- # cover the given outputs.
23
6
 
24
7
  class Pochette::TransactionBuilder
8
+ # Backend can be set globally, or independently for each class and instance.
9
+ class_attribute :backend
10
+ def self.backend
11
+ @backend || Pochette.backend
12
+ end
25
13
 
26
14
  cattr_accessor(:dust_size){ 546 }
27
15
  cattr_accessor(:output_size){ 149 }
@@ -30,6 +18,7 @@ class Pochette::TransactionBuilder
30
18
  cattr_accessor(:default_fee_per_kb){ 10000 }
31
19
 
32
20
  def initialize(options)
21
+ self.backend = options[:backend] if options[:backend]
33
22
  initialize_options(options)
34
23
  return unless valid?
35
24
  initialize_fee
@@ -42,10 +31,13 @@ class Pochette::TransactionBuilder
42
31
 
43
32
  def as_hash
44
33
  return nil unless valid?
45
- { amount: inputs_amount,
34
+ { input_total: inputs_amount,
35
+ output_total: outputs_amount,
46
36
  fee: inputs_amount - outputs_amount,
47
37
  inputs: inputs,
48
- outputs: outputs}
38
+ outputs: outputs,
39
+ utxos_to_blacklist: inputs.collect{|i| [i[1], i[2]] },
40
+ }
49
41
  end
50
42
 
51
43
  def valid?
@@ -44,11 +44,10 @@ class Pochette::TrezorTransactionBuilder < Pochette::TransactionBuilder
44
44
 
45
45
  def as_hash
46
46
  return nil unless valid?
47
- { amount: inputs_amount,
48
- fee: inputs_amount - outputs_amount,
49
- inputs: trezor_inputs,
50
- outputs: trezor_outputs,
51
- transactions: transactions}
47
+ super.merge(
48
+ trezor_inputs: trezor_inputs,
49
+ trezor_outputs: trezor_outputs,
50
+ transactions: transactions)
52
51
  end
53
52
 
54
53
  protected
@@ -1,3 +1,3 @@
1
1
  module Pochette
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.3"
3
3
  end
@@ -29,4 +29,5 @@ Gem::Specification.new do |spec|
29
29
  spec.add_development_dependency "rspec", "~> 3"
30
30
  spec.add_development_dependency "webmock", "~> 1.21"
31
31
  spec.add_development_dependency "timecop", "~> 0.8.0"
32
+ spec.add_development_dependency "byebug"
32
33
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pochette
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nubis
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-09-22 00:00:00.000000000 Z
12
+ date: 2015-09-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -123,6 +123,20 @@ dependencies:
123
123
  - - "~>"
124
124
  - !ruby/object:Gem::Version
125
125
  version: 0.8.0
126
+ - !ruby/object:Gem::Dependency
127
+ name: byebug
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
126
140
  description: "Pochette is a Bitcoin Wallet backend offering a common\n interface
127
141
  to several bitcoin nodes so you can build single purpose wallets.\n You can pass
128
142
  in a bunch of addresses and outputs and it will select the\n appropriate unspent
@@ -146,6 +160,7 @@ files:
146
160
  - bin/setup
147
161
  - lib/pochette.rb
148
162
  - lib/pochette/backends/bitcoin_core.rb
163
+ - lib/pochette/backends/blockchain_info.rb
149
164
  - lib/pochette/backends/trendy.rb
150
165
  - lib/pochette/transaction_builder.rb
151
166
  - lib/pochette/trezor_transaction_builder.rb