block_io 2.0.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/{.appveyor.yml → .appveyor.yml-disabled} +2 -2
- data/.gitignore +1 -0
- data/.travis.yml +1 -1
- data/LICENSE +1 -1
- data/README.md +14 -13
- data/block_io.gemspec +6 -6
- data/examples/basic.rb +29 -5
- data/examples/dtrust.rb +43 -24
- data/examples/sweeper.rb +21 -16
- data/lib/block_io.rb +3 -2
- data/lib/block_io/api_exception.rb +11 -0
- data/lib/block_io/chainparams/BTC.yml +8 -0
- data/lib/block_io/chainparams/BTCTEST.yml +8 -0
- data/lib/block_io/chainparams/DOGE.yml +8 -0
- data/lib/block_io/chainparams/DOGETEST.yml +8 -0
- data/lib/block_io/chainparams/LTC.yml +8 -0
- data/lib/block_io/chainparams/LTCTEST.yml +8 -0
- data/lib/block_io/client.rb +143 -79
- data/lib/block_io/extended_bitcoinrb.rb +127 -0
- data/lib/block_io/helper.rb +137 -39
- data/lib/block_io/key.rb +11 -124
- data/lib/block_io/version.rb +1 -1
- data/spec/client_misc_spec.rb +76 -0
- data/spec/client_spec.rb +23 -178
- data/spec/dtrust_spec.rb +167 -0
- data/spec/helper_spec.rb +1 -1
- data/spec/key_spec.rb +50 -19
- data/spec/larger_transaction_spec.rb +351 -0
- data/spec/sweep_spec.rb +115 -0
- data/spec/test-cases/.gitignore +2 -0
- data/spec/test-cases/LICENSE +21 -0
- data/spec/test-cases/README.md +2 -0
- data/spec/test-cases/json/create_and_sign_transaction_response.json +61 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json +1261 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json +1266 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json +1271 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json +3816 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2SH_3of5_195inputs.json +2931 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2SH_4of5_195inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_251inputs.json +3771 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_252inputs.json +3786 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_253inputs.json +3801 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_251inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_252inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_253inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_251inputs.json +3771 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_252inputs.json +3786 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_253inputs.json +3801 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_251inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_252inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_253inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_3_of_5_keys.json +21 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_4_of_5_keys.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_3_of_5_keys.json +21 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_4_of_5_keys.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3_of_5_keys.json +36 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_251outputs.json +591 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_252outputs.json +576 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_253outputs.json +531 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4_of_5_keys.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_251outputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_252outputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_253outputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2pkh.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh_over_p2sh.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +21 -0
- data/spec/test-cases/json/get_balance_response.json +8 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2SH_3of5_195inputs.json +1397 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2SH_4of5_195inputs.json +1397 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_251inputs.json +1795 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_252inputs.json +1802 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_253inputs.json +1809 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_251inputs.json +1789 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_252inputs.json +1802 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_253inputs.json +1809 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_251inputs.json +1795 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_252inputs.json +1802 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_253inputs.json +1809 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_251inputs.json +1795 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_252inputs.json +1802 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_253inputs.json +1809 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_p2sh.json +45 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_p2wsh_over_p2sh.json +45 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0.json +52 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_251outputs.json +1805 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_252outputs.json +1804 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_253outputs.json +1789 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_251outputs.json +1805 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_252outputs.json +1804 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_253outputs.json +1789 -0
- data/spec/test-cases/json/prepare_sweep_transaction_response_p2pkh.json +35 -0
- data/spec/test-cases/json/prepare_sweep_transaction_response_p2wpkh.json +35 -0
- data/spec/test-cases/json/prepare_sweep_transaction_response_p2wpkh_over_p2sh.json +35 -0
- data/spec/test-cases/json/prepare_transaction_response.json +164 -0
- data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json +1796 -0
- data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json +1803 -0
- data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json +1810 -0
- data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json +5367 -0
- data/spec/test-cases/json/prepare_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +76 -0
- data/spec/test-cases/json/summarize_prepared_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +6 -0
- metadata +187 -45
- data/examples/max_withdrawal.rb +0 -29
- data/lib/block_io/constants.rb +0 -10
- data/spec/data/sign_and_finalize_dtrust_withdrawal_request.json +0 -1
- data/spec/data/sign_and_finalize_sweep_request.json +0 -1
- data/spec/data/sign_and_finalize_withdrawal_request.json +0 -4
- data/spec/data/sweep_from_address_response.json +0 -1
- data/spec/data/withdraw_from_dtrust_address_response.json +0 -1
- data/spec/data/withdraw_response.json +0 -1227
- data/spec/rfc6979_spec.rb +0 -59
- data/spec/withdraw_spec.rb +0 -90
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7a878068a7ce224442ab3544657e2fb5de30dc68c43b00fd443c7c8ea35a61a
|
4
|
+
data.tar.gz: '09ad2302125ca6d86427637023439ffac4f8c1e7d6f477d098aeb6955d0f3be2'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb922e78d5c23c2e64b8259a688189eab1c20e654fa63389cc919d28a68030a0650101df2a32aecafbabb6ff639552e2698def07cd405638f0aea6bed32e7995
|
7
|
+
data.tar.gz: 940ff69d78033e939b150af65fc89944a8fd99a4296bab9c8314b5988508e2d093ec1e6c1295df37ebcbac4d31110fc70e3749d02a58da25585039594b872de6
|
@@ -4,8 +4,6 @@ skip_tags: true
|
|
4
4
|
|
5
5
|
environment:
|
6
6
|
matrix:
|
7
|
-
- ruby_version: "23"
|
8
|
-
- ruby_version: "23-x64"
|
9
7
|
- ruby_version: "24"
|
10
8
|
- ruby_version: "24-x64"
|
11
9
|
- ruby_version: "25"
|
@@ -14,6 +12,8 @@ environment:
|
|
14
12
|
- ruby_version: "26-x64"
|
15
13
|
- ruby_version: "27"
|
16
14
|
- ruby_version: "27-x64"
|
15
|
+
- ruby_version: "30"
|
16
|
+
- ruby_version: "30-x64"
|
17
17
|
|
18
18
|
install:
|
19
19
|
- SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# BlockIo
|
2
2
|
|
3
|
-
This Ruby Gem is the official reference client for the Block.io
|
3
|
+
This Ruby Gem is the official reference client for the Block.io's infrastructure APIs. To use this, you will need the Dogecoin, Bitcoin, or Litecoin API key(s) from <a href="https://block.io" target="_blank">Block.io</a>. Go ahead, sign up :)
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -14,29 +14,30 @@ And then execute:
|
|
14
14
|
|
15
15
|
Or install it yourself as:
|
16
16
|
|
17
|
-
$ gem install block_io -v=
|
17
|
+
$ gem install block_io -v=3.0.0
|
18
18
|
|
19
19
|
## Changelog
|
20
|
+
*04/14/21*: BREAKING CHANGES. Version 3.0.0. Remove support for Ruby < 2.4.0, and Windows. Behavior and interfaces have changed. By upgrading you'll need to revise your code and tests.
|
20
21
|
|
21
|
-
|
22
|
-
*
|
23
|
-
*
|
24
|
-
*
|
25
|
-
*
|
26
|
-
*
|
27
|
-
*10/18/14*: Now using deterministic signatures (RFC6979), and BIP62 to hinder transaction malleability.
|
28
|
-
|
22
|
+
## Important Notes
|
23
|
+
* This gem depends on the bitcoinrb gem. By using this gem, your application will load the bitcoinrb gem as well with the Bitcoin namespace. It may conflict with another gem using the same namespace.
|
24
|
+
* Transaction endpoints are updated as of v3.0.0.
|
25
|
+
* See the examples/ folder for basic examples.
|
26
|
+
* Be careful to test thoroughly before production.
|
27
|
+
* Use of this software is subject to its LICENSE.
|
29
28
|
|
30
29
|
## Usage
|
31
30
|
|
32
31
|
It's super easy to get started. In your Ruby shell ($ irb), for example, do this:
|
33
32
|
|
34
33
|
require 'block_io'
|
35
|
-
blockio = BlockIo::Client.new(:api_key => "API KEY", :pin => "SECRET PIN")
|
34
|
+
blockio = BlockIo::Client.new(:api_key => "API KEY", :pin => "SECRET PIN")
|
36
35
|
|
37
|
-
If you do not have your PIN, or just wish to use your private key backup directly, do this:
|
36
|
+
If you do not have your PIN, or just wish to use your private key backup(s) directly, do this instead:
|
38
37
|
|
39
|
-
blockio = BlockIo::Client.new(:api_key => "API KEY"
|
38
|
+
blockio = BlockIo::Client.new(:api_key => "API KEY")
|
39
|
+
blockio.get_balance
|
40
|
+
blockio.prepare_transaction(..., :keys => [BlockIo::Key.from_wif("PRIVATE_KEY_BACKUP_IN_WIF").private_key_hex])
|
40
41
|
|
41
42
|
And you're good to go:
|
42
43
|
|
data/block_io.gemspec
CHANGED
@@ -15,15 +15,15 @@ Gem::Specification.new do |spec|
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0")
|
16
16
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
-
spec.required_ruby_version = '>= 2.
|
18
|
+
spec.required_ruby_version = '>= 2.4.0'
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", ">= 1.16", "< 3.0"
|
22
|
-
spec.add_development_dependency "rake", "~>
|
22
|
+
spec.add_development_dependency "rake", "~> 13.0", ">= 13.0"
|
23
23
|
spec.add_development_dependency "rspec", "~> 3.6", ">= 3.6"
|
24
|
-
spec.add_development_dependency "webmock", "~> 3.
|
25
|
-
spec.add_runtime_dependency "
|
24
|
+
spec.add_development_dependency "webmock", "~> 3.12", "< 4.0"
|
25
|
+
spec.add_runtime_dependency "bitcoinrb", "~> 0.7.0", "= 0.7.0"
|
26
26
|
spec.add_runtime_dependency "http", "~> 4.4.1", ">= 4.4.1"
|
27
|
-
spec.add_runtime_dependency "oj", "~> 3.
|
28
|
-
spec.add_runtime_dependency "connection_pool", "~> 2.2.
|
27
|
+
spec.add_runtime_dependency "oj", "~> 3.11", ">= 3.11"
|
28
|
+
spec.add_runtime_dependency "connection_pool", "~> 2.2.5", ">= 2.2"
|
29
29
|
end
|
data/examples/basic.rb
CHANGED
@@ -3,18 +3,42 @@
|
|
3
3
|
# basic example: $ API_KEY=TESTNET_API_KEY PIN=YOUR_SECRET_PIN ruby basic.rb
|
4
4
|
# bundler example: $ API_KEY=TESTNET_API_KEY PIN=YOUR_SECRET_PIN bundle exec ruby basic.rb
|
5
5
|
#
|
6
|
-
# adjust amount below if not using the
|
6
|
+
# adjust amount below if not using the Litecoin Testnet
|
7
7
|
|
8
8
|
require 'block_io'
|
9
9
|
|
10
|
-
blockio = BlockIo::Client.new(:api_key => ENV['API_KEY'], :pin => ENV['PIN']
|
10
|
+
blockio = BlockIo::Client.new(:api_key => ENV['API_KEY'], :pin => ENV['PIN'])
|
11
11
|
puts blockio.get_balance
|
12
12
|
puts blockio.network
|
13
13
|
|
14
14
|
# create the address if it doesn't exist
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
begin
|
16
|
+
puts blockio.get_new_address(:label => 'testDest')
|
17
|
+
rescue BlockIo::APIException => e
|
18
|
+
puts e.to_s
|
19
|
+
end
|
20
|
+
puts " -- "
|
21
|
+
|
22
|
+
# retrieve unspent outputs and other relevant data to create and sign the transaction
|
23
|
+
# you will inspect the prepared transaction for things like network fees being paid, block.io fees being paid, validating what destination addresses receive how much, etc.
|
24
|
+
prepared_transaction = blockio.prepare_transaction(:to_label => 'testDest', :amount => '0.012345')
|
25
|
+
puts JSON.pretty_generate(prepared_transaction)
|
26
|
+
puts " -- "
|
27
|
+
|
28
|
+
# a summary of what's in the prepared transaction
|
29
|
+
# for in-depth review of the transaction, look at the prepared_transaction object yourself
|
30
|
+
puts JSON.pretty_generate(blockio.summarize_prepared_transaction(prepared_transaction))
|
31
|
+
puts " -- "
|
32
|
+
|
33
|
+
# once satisfied with the prepared transaction, create and sign it
|
34
|
+
# inspect this again if you wish, it will contain the transaction payload in hexadecimal form you want Block.io to sign and broadcast
|
35
|
+
transaction_data = blockio.create_and_sign_transaction(prepared_transaction)
|
36
|
+
puts JSON.pretty_generate(transaction_data)
|
37
|
+
puts " -- "
|
38
|
+
|
39
|
+
# ask Block.io to sign and broadcast the transaction
|
40
|
+
puts JSON.pretty_generate(blockio.submit_transaction(:transaction_data => transaction_data))
|
41
|
+
puts " -- "
|
18
42
|
|
19
43
|
puts blockio.get_address_balance(:labels => 'default,testDest')
|
20
44
|
|
data/examples/dtrust.rb
CHANGED
@@ -1,25 +1,31 @@
|
|
1
|
-
# creates a new destination address,
|
1
|
+
# creates a new dTrust destination address, sends coins to it, withdraws coins from it, gets sent transactions, and the current price
|
2
2
|
|
3
3
|
require 'block_io'
|
4
4
|
require 'json'
|
5
5
|
|
6
6
|
# please use the Litecoin Testnet API key here
|
7
7
|
puts "*** Initialize BlockIo library: "
|
8
|
-
blockio = BlockIo::Client.new(:api_key => ENV['API_KEY'], :pin => ENV['PIN']
|
8
|
+
blockio = BlockIo::Client.new(:api_key => ENV['API_KEY'], :pin => ENV['PIN'])
|
9
|
+
|
9
10
|
puts blockio.get_dtrust_balance
|
10
11
|
puts blockio.network
|
11
12
|
|
12
13
|
raise "Please use the LTCTEST network API Key here or modify this script for another network." unless blockio.network == "LTCTEST"
|
13
14
|
|
14
15
|
# create 4 keys
|
15
|
-
# you will generate your own private keys, for instance: key = BlockIo::Key.
|
16
|
-
#
|
16
|
+
# you will generate your own private keys, for instance: key = BlockIo::Key.generate.
|
17
|
+
# you will need to record key.to_wif (private key in Wallet Import Format (WIF)) somewhere safe before you use your keys to generate dTrust addresses.
|
18
|
+
# if you already have hex private keys, load the keys (see below). Ensure the key's public_key (key.public_key_hex) matches what you expect.
|
17
19
|
# WARNING: The keys below are just for demonstration, DO NOT use them on mainnets, DO NOT use insecurely generated keys
|
20
|
+
# WARNING: You must ALWAYS use compressed public keys. Use of uncompressed public keys can lead to lost coins when using SegWit addresses.
|
21
|
+
|
22
|
+
# these keys will use the appropriate coin's parameters. The library will know what network you're interacting with once you make a successful API call first, like blockio.get_dtrust_balance above
|
23
|
+
|
18
24
|
keys = [
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
25
|
+
"b515fd806a662e061b488e78e5d0c2ff46df80083a79818e166300666385c0a2", # alpha1alpha2alpha3alpha4
|
26
|
+
"1584b821c62ecdc554e185222591720d6fe651ed1b820d83f92cdc45c5e21f", # alpha2alpha3alpha4alpha1
|
27
|
+
"2f9090b8aa4ddb32c3b0b8371db1b50e19084c720c30db1d6bb9fcd3a0f78e61", # alpha3alpha4alpha1alpha2
|
28
|
+
"6c1cefdfd9187b36b36c3698c1362642083dcc1941dc76d751481d3aa29ca65" # alpha4alpha1alpha2alpha3
|
23
29
|
].freeze
|
24
30
|
|
25
31
|
dtrust_address = nil
|
@@ -29,15 +35,13 @@ begin
|
|
29
35
|
# let's create a new address with all 4 keys as signers, but only 3 signers required (i.e., 4 of 5 multisig, with 1 signature being Block.io)
|
30
36
|
# you will need all 4 of your keys to use your address without interacting with Block.io
|
31
37
|
|
32
|
-
signers = keys.map{|
|
38
|
+
signers = keys.map{|x| BlockIo::Key.from_private_key_hex(x)}.map(&:public_key_hex).join(',')
|
33
39
|
|
34
40
|
response = blockio.get_new_dtrust_address(:label => dtrust_address_label, :public_keys => signers, :required_signatures => 3, :address_type => "witness_v0")
|
35
41
|
|
36
42
|
dtrust_address = response['data']['address']
|
37
43
|
|
38
|
-
|
39
|
-
|
40
|
-
rescue Exception => e
|
44
|
+
rescue BlockIo::APIException => e
|
41
45
|
# if this failed, we probably created the same label before. let's fetch the address then.
|
42
46
|
puts e.to_s
|
43
47
|
|
@@ -49,7 +53,21 @@ end
|
|
49
53
|
puts "*** Our dTrust Address: #{dtrust_address}"
|
50
54
|
|
51
55
|
# let's deposit some coins into this new address
|
52
|
-
|
56
|
+
|
57
|
+
# blockio.prepare_transaction gets the appropriate data you need to create and sign your transaction. You will need to inspect it to ensure things are as expected yourself.
|
58
|
+
prepared_transaction = blockio.prepare_transaction(:to_address => dtrust_address, :amount => '0.001')
|
59
|
+
|
60
|
+
puts JSON.pretty_generate(prepared_transaction)
|
61
|
+
puts " -- "
|
62
|
+
|
63
|
+
puts JSON.pretty_generate(blockio.summarize_prepared_transaction(prepared_transaction))
|
64
|
+
puts " -- "
|
65
|
+
|
66
|
+
# blockio.create_and_sign_transaction creates the transaction client-side, and appends your signatures (if any)
|
67
|
+
transaction_data = blockio.create_and_sign_transaction(prepared_transaction)
|
68
|
+
|
69
|
+
# blockio.submit_transaction sends the signatures and transaction payload to Block.io so Block.io can add its signatures and broadcast the transaction to the network
|
70
|
+
response = blockio.submit_transaction(:transaction_data => transaction_data)
|
53
71
|
|
54
72
|
puts "*** Withdrawal response:"
|
55
73
|
puts JSON.pretty_generate(response)
|
@@ -64,25 +82,26 @@ normal_address = blockio.get_address_by_label(:label => 'default')['data']['addr
|
|
64
82
|
|
65
83
|
puts "*** Withdrawing from dtrust_address_label to the 'default' label in normal multisig"
|
66
84
|
|
67
|
-
|
68
|
-
|
69
|
-
|
85
|
+
# note use of prepare_dtrust_transaction instead of prepare_transaction, since this is a dTrust transaction
|
86
|
+
# we're not doing any inspection here, but you will in your own code
|
87
|
+
prepared_transaction = blockio.prepare_dtrust_transaction(:from_labels => dtrust_address_label, :to_addresses => normal_address, :amounts => '0.0009')
|
70
88
|
|
71
|
-
#
|
72
|
-
|
89
|
+
# create the transaction and sign it with the keys we've provided
|
90
|
+
# we're signing the transaction partially by supplying only 3 of our 4 keys
|
91
|
+
transaction_data = blockio.create_and_sign_transaction(prepared_transaction, keys.first(3))
|
73
92
|
|
74
|
-
puts "***
|
93
|
+
puts "*** Submitting transaction data:"
|
94
|
+
puts JSON.pretty_generate(transaction_data)
|
75
95
|
|
76
|
-
|
77
|
-
|
96
|
+
# if successful, you will get a transaction ID when you submit_transaction.
|
97
|
+
response = blockio.submit_transaction(:transaction_data => transaction_data)
|
78
98
|
|
79
99
|
# let's final the withdrawal
|
80
|
-
puts "***
|
81
|
-
puts JSON.pretty_generate(
|
100
|
+
puts "*** Submit the transaction: "
|
101
|
+
puts JSON.pretty_generate(response)
|
82
102
|
|
83
103
|
# get the sent transactions for this dTrust address
|
84
104
|
|
85
105
|
puts "*** Get transactions sent by our dtrust_address_label address: "
|
86
|
-
|
87
106
|
puts JSON.pretty_generate(blockio.get_dtrust_transactions(:type => 'sent', :labels => dtrust_address_label))
|
88
107
|
|
data/examples/sweeper.rb
CHANGED
@@ -4,26 +4,31 @@
|
|
4
4
|
#
|
5
5
|
# Contact support@block.io if you have any issues
|
6
6
|
|
7
|
-
require
|
7
|
+
require 'block_io'
|
8
8
|
|
9
|
-
blockio = BlockIo::Client.new(:api_key => ENV['API_KEY']
|
10
|
-
puts blockio.get_balance
|
11
|
-
puts blockio.network
|
9
|
+
blockio = BlockIo::Client.new(:api_key => ENV['API_KEY'])
|
12
10
|
|
13
11
|
to_address = ENV['TO_ADDRESS'] # sweep coins into this address
|
12
|
+
private_key = ENV['PRIVATE_KEY'] # private key for the address from which you wish to sweep coins (WIF)
|
14
13
|
|
15
|
-
|
16
|
-
|
14
|
+
# prepare the sweep transaction
|
15
|
+
# you will inspect this data to ensure things are in order (the network fees you pay, the amount being swept, etc.)
|
16
|
+
# the private key is used to determine the public key by prepare_sweep_transaction client-side
|
17
|
+
# the private key never travels to Block.io
|
18
|
+
prepared_transaction = blockio.prepare_sweep_transaction(:to_address => to_address, :private_key => private_key)
|
19
|
+
puts JSON.pretty_generate(prepared_transaction)
|
20
|
+
puts " -- "
|
17
21
|
|
18
|
-
|
19
|
-
|
22
|
+
# create and sign the transaction
|
23
|
+
# the signature is from the key you provided to prepare_sweep_transaction above
|
24
|
+
transaction_data = blockio.create_and_sign_transaction(prepared_transaction)
|
25
|
+
puts JSON.pretty_generate(transaction_data)
|
26
|
+
puts " -- "
|
20
27
|
|
21
|
-
|
28
|
+
# submit the final transaction to Block.io for broadcast to the network, or
|
29
|
+
# submit the transaction payload youself elsewhere (like using sendrawtransaction RPC calls with bitcoind, dogecoind, litecoind, etc.)
|
30
|
+
response = blockio.submit_transaction(:transaction_data => transaction_data)
|
31
|
+
puts JSON.pretty_generate(response)
|
32
|
+
puts " -- "
|
22
33
|
|
23
|
-
|
24
|
-
puts "Transaction ID: #{response['data']['txid']}"
|
25
|
-
puts "Network Fee Incurred: #{response['data']['network_fee']} #{response['data']['network']}"
|
26
|
-
|
27
|
-
rescue Exception => e
|
28
|
-
puts "Sweep failed: #{e}"
|
29
|
-
end
|
34
|
+
puts "Transaction ID: #{response['data']['txid']}"
|
data/lib/block_io.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
require "http"
|
2
2
|
require "oj"
|
3
|
-
require "
|
3
|
+
require "bitcoin"
|
4
4
|
require "openssl"
|
5
5
|
require "securerandom"
|
6
6
|
require "connection_pool"
|
7
7
|
|
8
8
|
require_relative "block_io/version"
|
9
|
-
require_relative "block_io/constants"
|
10
9
|
require_relative "block_io/helper"
|
11
10
|
require_relative "block_io/key"
|
12
11
|
require_relative "block_io/client"
|
12
|
+
require_relative "block_io/api_exception"
|
13
|
+
require_relative "block_io/extended_bitcoinrb"
|
13
14
|
|
14
15
|
module BlockIo
|
15
16
|
|
data/lib/block_io/client.rb
CHANGED
@@ -20,16 +20,8 @@ module BlockIo
|
|
20
20
|
@version = args[:version] || 2
|
21
21
|
@hostname = args[:hostname] || "block.io"
|
22
22
|
@proxy = args[:proxy] || {}
|
23
|
-
@keys =
|
24
|
-
@use_low_r = args[:use_low_r]
|
25
|
-
@raise_exception_on_error = args[:raise_exception_on_error] || false
|
23
|
+
@keys = {}
|
26
24
|
|
27
|
-
raise Exception.new("Keys must be provided as an array.") unless @keys.is_a?(Array)
|
28
|
-
raise Exception.new("Keys must be BlockIo::Key objects.") unless @keys.all?{|key| key.is_a?(BlockIo::Key)}
|
29
|
-
|
30
|
-
# make a hash of the keys we've been given
|
31
|
-
@keys = @keys.inject({}){|h,v| h[v.public_key] = v; h}
|
32
|
-
|
33
25
|
raise Exception.new("Must specify hostname, port, username, password if using a proxy.") if @proxy.keys.size > 0 and [:hostname, :port, :username, :password].any?{|x| !@proxy.key?(x)}
|
34
26
|
|
35
27
|
@conn = ConnectionPool.new(:size => args[:pool_size] || 5) { http = HTTP.headers(:accept => "application/json", :user_agent => "gem:block_io:#{VERSION}");
|
@@ -50,109 +42,175 @@ module BlockIo
|
|
50
42
|
raise Exception.new("Parameter keys must be symbols. For instance: :label => 'default' instead of 'label' => 'default'") unless args[0].nil? or args[0].keys.all?{|x| x.is_a?(Symbol)}
|
51
43
|
raise Exception.new("Cannot pass PINs to any calls. PINs can only be set when initiating this library.") if !args[0].nil? and args[0].key?(:pin)
|
52
44
|
raise Exception.new("Do not specify API Keys here. Initiate a new BlockIo object instead if you need to use another API Key.") if !args[0].nil? and args[0].key?(:api_key)
|
53
|
-
|
54
|
-
if
|
55
|
-
#
|
56
|
-
|
57
|
-
elsif BlockIo::SWEEP_METHODS.key?(method_name) then
|
45
|
+
|
46
|
+
if method_name.eql?("prepare_sweep_transaction") then
|
47
|
+
# we need to ensure @network is set before we allow this
|
48
|
+
# we need to send only the public key, not the given private key
|
58
49
|
# we're sweeping from an address
|
59
|
-
|
60
|
-
elsif BlockIo::FINALIZE_SIGNATURE_METHODS.key?(method_name) then
|
61
|
-
# we're finalize the transaction signatures
|
62
|
-
finalize_signature(args[0], method_name)
|
50
|
+
internal_prepare_sweep_transaction(args[0], method_name)
|
63
51
|
else
|
64
52
|
api_call({:method_name => method_name, :params => args[0] || {}})
|
65
53
|
end
|
66
54
|
|
67
55
|
end
|
68
56
|
|
69
|
-
|
70
|
-
|
71
|
-
|
57
|
+
def summarize_prepared_transaction(data)
|
58
|
+
# takes the response from prepare_transaction/prepare_dtrust_transaction/prepare_sweep_transaction
|
59
|
+
# returns the network fee being paid, the blockio fee being paid, amounts being sent
|
72
60
|
|
73
|
-
|
61
|
+
input_sum = data['data']['inputs'].map{|input| BigDecimal(input['input_value'])}.inject(:+)
|
74
62
|
|
75
|
-
|
76
|
-
|
63
|
+
output_values = [BigDecimal(0)]
|
64
|
+
blockio_fees = [BigDecimal(0)]
|
65
|
+
change_amounts = [BigDecimal(0)]
|
77
66
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
67
|
+
data['data']['outputs'].each do |output|
|
68
|
+
if output['output_category'] == 'blockio-fee' then
|
69
|
+
blockio_fees << BigDecimal(output['output_value'])
|
70
|
+
elsif output['output_category'] == 'change' then
|
71
|
+
change_amounts << BigDecimal(output['output_value'])
|
72
|
+
else
|
73
|
+
# user-specified
|
74
|
+
output_values << BigDecimal(output['output_value'])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
output_sum = output_values.inject(:+)
|
79
|
+
blockio_fee = blockio_fees.inject(:+)
|
80
|
+
change_amount = change_amounts.inject(:+)
|
81
|
+
|
82
|
+
network_fee = input_sum - output_sum - blockio_fee - change_amount
|
83
|
+
|
84
|
+
{
|
85
|
+
'network' => data['data']['network'],
|
86
|
+
'network_fee' => '%0.8f' % network_fee,
|
87
|
+
"blockio_fee" => '%0.8f' % blockio_fee,
|
88
|
+
"total_amount_to_send" => '%0.8f' % output_sum
|
89
|
+
}
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
def create_and_sign_transaction(data, keys = [])
|
94
|
+
# takes data from prepare_transaction, prepare_dtrust_transaction, prepare_sweep_transaction
|
95
|
+
# creates the transaction given the inputs and outputs from data
|
96
|
+
# signs the transaction using keys (if not provided, decrypts the key using the PIN)
|
97
|
+
|
98
|
+
set_network(data['data']['network']) if data['data'].key?('network')
|
82
99
|
|
83
|
-
|
100
|
+
raise "Data must be contain one or more inputs" unless data['data']['inputs'].size > 0
|
101
|
+
raise "Data must contain one or more outputs" unless data['data']['outputs'].size > 0
|
102
|
+
raise "Data must contain information about addresses" unless data['data']['input_address_data'].size > 0 # TODO make stricter
|
84
103
|
|
85
|
-
|
86
|
-
raise Exception.new("Public key mismatch for requested signer and ourselves. Invalid Secret PIN detected.") unless key.public_key.eql?(encrypted_passphrase["signer_public_key"])
|
104
|
+
private_keys = keys.map{|x| Key.from_private_key_hex(x)}
|
87
105
|
|
88
|
-
|
89
|
-
|
106
|
+
# TODO debug all of this
|
107
|
+
|
108
|
+
inputs = data['data']['inputs']
|
109
|
+
outputs = data['data']['outputs']
|
90
110
|
|
91
|
-
|
111
|
+
tx = Bitcoin::Tx.new
|
92
112
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
# we just need reference_id and inputs
|
98
|
-
response["data"] = {"reference_id" => response["data"]["reference_id"], "inputs" => response["data"]["inputs"]}
|
99
|
-
|
100
|
-
# let's sign all the inputs we can
|
101
|
-
signatures_added = (@keys.size == 0 ? false : Helper.signData(response["data"]["inputs"], @keys))
|
102
|
-
|
103
|
-
# the response object is now signed, let's stringify it and finalize this withdrawal
|
104
|
-
response = finalize_signature({:signature_data => response["data"]}, "sign_and_finalize_withdrawal") if signatures_added
|
105
|
-
|
106
|
-
# if we provided all the required signatures, this transaction went through
|
107
|
-
# otherwise Block.io responded with data asking for more signatures and recorded the signature we provided above
|
108
|
-
# the latter will be the case for dTrust addresses
|
109
|
-
end
|
110
|
-
|
113
|
+
# populate the inputs
|
114
|
+
inputs.each do |input|
|
115
|
+
tx.in << Bitcoin::TxIn.new(:out_point => Bitcoin::OutPoint.from_txid(input['previous_txid'], input['previous_output_index']))
|
111
116
|
end
|
112
117
|
|
113
|
-
|
118
|
+
# populate the outputs
|
119
|
+
outputs.each do |output|
|
120
|
+
tx.out << Bitcoin::TxOut.new(:value => (BigDecimal(output['output_value']) * BigDecimal(100000000)).to_i, :script_pubkey => Bitcoin::Script.parse_from_addr(output['receiving_address']))
|
121
|
+
end
|
114
122
|
|
115
|
-
end
|
116
123
|
|
117
|
-
|
118
|
-
|
124
|
+
# some protection against misbehaving machines and/or code
|
125
|
+
raise Exception.new("Expected unsigned transaction ID mismatch. Please report this error to support@block.io.") unless (data['data']['expected_unsigned_txid'].nil? or
|
126
|
+
data['data']['expected_unsigned_txid'] == tx.txid)
|
119
127
|
|
120
|
-
|
128
|
+
# extract key
|
129
|
+
encrypted_key = data['data']['user_key']
|
121
130
|
|
122
|
-
|
123
|
-
|
124
|
-
sanitized_args.delete(:private_key)
|
125
|
-
|
126
|
-
response = api_call({:method_name => method_name, :params => sanitized_args})
|
127
|
-
|
128
|
-
if response["data"].key?("reference_id") then
|
129
|
-
# Block.io's asking us to provide client-side signatures
|
131
|
+
if !encrypted_key.nil? and !@keys.key?(encrypted_key['public_key']) then
|
132
|
+
# decrypt the key with PIN
|
130
133
|
|
131
|
-
|
132
|
-
response["data"] = {"reference_id" => response["data"]["reference_id"], "inputs" => response["data"]["inputs"]}
|
134
|
+
raise Exception.new("PIN not set and no keys provided. Cannot sign transaction.") unless @encryption_key or @keys.size > 0
|
133
135
|
|
134
|
-
|
135
|
-
|
136
|
+
key = Helper.extractKey(encrypted_key['encrypted_passphrase'], @encryption_key)
|
137
|
+
raise Exception.new("Public key mismatch for requested signer and ourselves. Invalid Secret PIN detected.") unless key.public_key_hex.eql?(encrypted_key["public_key"])
|
136
138
|
|
137
|
-
#
|
138
|
-
|
139
|
+
# store this key for later use
|
140
|
+
@keys[key.public_key_hex] = key
|
141
|
+
|
142
|
+
end
|
139
143
|
|
140
|
-
|
144
|
+
# store the provided keys, if any, for later use
|
145
|
+
private_keys.each{|key| @keys[key.public_key_hex] = key}
|
146
|
+
|
147
|
+
signatures = []
|
148
|
+
|
149
|
+
if @keys.size > 0 then
|
150
|
+
# try to sign whatever we can here and give the user the data back
|
151
|
+
# Block.io will check to see if all signatures are present, or return an error otherwise saying insufficient signatures provided
|
152
|
+
|
153
|
+
i = 0
|
154
|
+
while i < inputs.size do
|
155
|
+
input = inputs[i]
|
156
|
+
|
157
|
+
input_address_data = data['data']['input_address_data'].detect{|d| d['address'] == input['spending_address']}
|
158
|
+
sighash_for_input = Helper.getSigHashForInput(tx, i, input, input_address_data) # in bytes
|
159
|
+
|
160
|
+
input_address_data['public_keys'].each do |signer_public_key|
|
161
|
+
# sign what we can and append signatures to the signatures object
|
162
|
+
|
163
|
+
next unless @keys.key?(signer_public_key)
|
164
|
+
|
165
|
+
signature = @keys[signer_public_key].sign(sighash_for_input).unpack("H*")[0] # in hex
|
166
|
+
signatures << {"input_index" => i, "public_key" => signer_public_key, "signature" => signature}
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
i += 1 # go to next input
|
171
|
+
end
|
172
|
+
|
141
173
|
end
|
142
174
|
|
143
|
-
|
175
|
+
# if we have everything we need for this transaction, just finalize the transaction
|
176
|
+
if Helper.allSignaturesPresent?(tx, inputs, signatures, data['data']['input_address_data']) then
|
177
|
+
Helper.finalizeTransaction(tx, inputs, signatures, data['data']['input_address_data'])
|
178
|
+
signatures = [] # no signatures left to append
|
179
|
+
end
|
144
180
|
|
181
|
+
# reset keys
|
182
|
+
@keys = {}
|
183
|
+
|
184
|
+
# the response for submitting the transaction
|
185
|
+
{"tx_type" => data['data']['tx_type'], "tx_hex" => tx.to_hex, "signatures" => (signatures.size == 0 ? nil : signatures)}
|
186
|
+
|
145
187
|
end
|
146
188
|
|
147
|
-
|
189
|
+
private
|
190
|
+
|
191
|
+
def internal_prepare_sweep_transaction(args = {}, method_name = "prepare_sweep_transaction")
|
148
192
|
|
149
|
-
|
193
|
+
# set the network first if not already known
|
194
|
+
api_call({:method_name => "get_balance", :params => {}}) if @network.nil?
|
150
195
|
|
151
|
-
|
196
|
+
raise Exception.new("No private_key provided.") unless args.key?(:private_key) and (args[:private_key] || "").size > 0
|
152
197
|
|
153
|
-
|
198
|
+
# ensure the private key never goes to Block.io
|
199
|
+
key = Key.from_wif(args[:private_key])
|
200
|
+
sanitized_args = args.merge({:public_key => key.public_key_hex})
|
201
|
+
sanitized_args.delete(:private_key)
|
202
|
+
|
203
|
+
@keys[key.public_key_hex] = key # store this in our set of keys for later use
|
204
|
+
|
205
|
+
api_call({:method_name => method_name, :params => sanitized_args})
|
154
206
|
|
155
207
|
end
|
208
|
+
|
209
|
+
def set_network(network)
|
210
|
+
# load the chain_params for this network
|
211
|
+
@network ||= network
|
212
|
+
Bitcoin.chain_params = @network unless @network.to_s.size == 0
|
213
|
+
end
|
156
214
|
|
157
215
|
def api_call(args)
|
158
216
|
|
@@ -166,9 +224,15 @@ module BlockIo
|
|
166
224
|
body = {"status" => "fail", "data" => {"error_message" => "Unknown error occurred. Please report this to support@block.io. Status #{response.code}."}}
|
167
225
|
end
|
168
226
|
|
169
|
-
|
227
|
+
if !body["status"].eql?("success") then
|
228
|
+
# raise an exception on error for easy handling
|
229
|
+
# user can extract raw response using e.raw_data
|
230
|
+
e = APIException.new("#{body["data"]["error_message"]}")
|
231
|
+
e.set_raw_data(body)
|
232
|
+
raise e
|
233
|
+
end
|
170
234
|
|
171
|
-
|
235
|
+
set_network(body['data']['network']) if body['data'].key?('network')
|
172
236
|
|
173
237
|
body
|
174
238
|
|