nanook 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 96226472bd51f8f70ef9462fa26ddf6b6f16da85
4
+ data.tar.gz: 014690cf0466ed78b0aa95a92ad9dd2f15a7581b
5
+ SHA512:
6
+ metadata.gz: 5571c48e4ce49a3e75487d7632d51ed4b7b5edc5ccb3aed14e3c7e18f92cd18c582606e2152ea4eb21bca238a077068a48b12db1036a43043d924c29e112fff2
7
+ data.tar.gz: cf515b77fe27f4cb882a36a04b8c109943388cdcd33db3c34a018f586a7c41f8c0a8dc406a048048dcfad21edde62bbae6e468d0131efa96202990f45eb170f1
@@ -0,0 +1,56 @@
1
+ # Ruby CircleCI 2.0 configuration file
2
+ #
3
+ # Check https://circleci.com/docs/2.0/language-ruby/ for more details
4
+ #
5
+ version: 2
6
+ jobs:
7
+ build:
8
+ docker:
9
+ # specify the version you desire here
10
+ - image: circleci/ruby:2.4.1-node-browsers
11
+
12
+ # Specify service dependencies here if necessary
13
+ # CircleCI maintains a library of pre-built images
14
+ # documented at https://circleci.com/docs/2.0/circleci-images/
15
+ # - image: circleci/postgres:9.4
16
+
17
+ working_directory: ~/repo
18
+
19
+ steps:
20
+ - checkout
21
+
22
+ # Download and cache dependencies
23
+ - restore_cache:
24
+ keys:
25
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}
26
+ # fallback to using the latest cache if no exact match is found
27
+ - v1-dependencies-
28
+
29
+ - run:
30
+ name: install dependencies
31
+ command: |
32
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
33
+
34
+ - save_cache:
35
+ paths:
36
+ - ./vendor/bundle
37
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}
38
+
39
+ # Database setup
40
+ # - run: bundle exec rake db:create
41
+ # - run: bundle exec rake db:schema:load
42
+
43
+ # run tests!
44
+ - run:
45
+ name: run tests
46
+ command: |
47
+ mkdir /tmp/test-results
48
+
49
+ bundle exec rspec spec --format progress --out /tmp/test-results/rspec.xml
50
+
51
+ # collect reports
52
+ - store_test_results:
53
+ path: /tmp/test-results
54
+ - store_artifacts:
55
+ path: /tmp/test-results
56
+ destination: test-results
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in nanook.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,49 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ nanook (0.6.0)
5
+ symbolized
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ addressable (2.5.2)
11
+ public_suffix (>= 2.0.2, < 4.0)
12
+ crack (0.4.3)
13
+ safe_yaml (~> 1.0.0)
14
+ diff-lcs (1.3)
15
+ hashdiff (0.3.7)
16
+ public_suffix (3.0.2)
17
+ rake (10.5.0)
18
+ rspec (3.7.0)
19
+ rspec-core (~> 3.7.0)
20
+ rspec-expectations (~> 3.7.0)
21
+ rspec-mocks (~> 3.7.0)
22
+ rspec-core (3.7.1)
23
+ rspec-support (~> 3.7.0)
24
+ rspec-expectations (3.7.0)
25
+ diff-lcs (>= 1.2.0, < 2.0)
26
+ rspec-support (~> 3.7.0)
27
+ rspec-mocks (3.7.0)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.7.0)
30
+ rspec-support (3.7.1)
31
+ safe_yaml (1.0.4)
32
+ symbolized (0.0.1)
33
+ webmock (3.3.0)
34
+ addressable (>= 2.3.6)
35
+ crack (>= 0.3.2)
36
+ hashdiff
37
+
38
+ PLATFORMS
39
+ ruby
40
+
41
+ DEPENDENCIES
42
+ bundler (~> 1.16)
43
+ nanook!
44
+ rake (~> 10.0)
45
+ rspec (~> 3.2)
46
+ webmock (~> 3.3)
47
+
48
+ BUNDLED WITH
49
+ 1.16.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Luke Duncalfe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,304 @@
1
+ # Nanook
2
+
3
+ This is a Ruby library for managing a [nano currency](https://nano.org/) node, including making and receiving payments, using the [nano RPC protocol](https://github.com/nanocurrency/raiblocks/wiki/RPC-protocol). Nano is a fee-less, fast, environmentally-friendly cryptocurrency. It's awesome. See [https://nano.org/](https://nano.org/).
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/nanook.svg)](https://badge.fury.io/rb/nanook)
6
+ [![CircleCI](https://circleci.com/gh/lukes/nanook/tree/master.svg?style=shield)](https://circleci.com/gh/lukes/nanook/tree/master)
7
+
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'nanook'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install nanook
24
+
25
+ # Getting Started
26
+
27
+ ## Initializing
28
+
29
+ Nanook will by default connect to `http://localhost:7076`. If you're using Nanook from the nano node itself this will generally work fine.
30
+
31
+ ```ruby
32
+ nanook = Nanook.new
33
+ ```
34
+
35
+ To connect to another host instead:
36
+
37
+ ```ruby
38
+ nanook = Nanook.new("http://example.com:7076")
39
+ ```
40
+
41
+ ## Basics
42
+
43
+ ### Working with wallets and accounts
44
+
45
+ Create a wallet:
46
+
47
+ ```ruby
48
+ Nanook.new.wallet.create
49
+ ```
50
+
51
+ Create an account within a wallet:
52
+
53
+ ```ruby
54
+ Nanook.new.wallet(wallet_id).account.create
55
+ ```
56
+
57
+ List accounts within a wallet:
58
+
59
+ ```ruby
60
+ Nanook.new.wallet(wallet_id).accounts
61
+ ```
62
+
63
+ ### Sending a payment
64
+
65
+ You can send a payment from an account in a wallet:
66
+
67
+ ```ruby
68
+ account = Nanook.new.wallet(wallet_id).account(account_id)
69
+ account.pay(to: recipient_account_id, amount: 0.2, id: unique_id)
70
+ ```
71
+
72
+ Or, a wallet:
73
+
74
+ ```ruby
75
+ wallet = Nanook.new.wallet(wallet_id)
76
+ wallet.pay(from: your_account_id, to: recipient_account_id, amount: 0.2, id: unique_id)
77
+ ```
78
+
79
+ The `id` can be any string and needs to be unique per payment. It serves an important purpose; it allows you to make this call multiple times with the same `id` and be reassured that you will only ever send that nano payment once. From the [Nano RPC](https://github.com/nanocurrency/raiblocks/wiki/RPC-protocol#account-create):
80
+
81
+ > You can (and should) specify a unique id for each spend to provide idempotency. That means that if you [make the payment call] two times with the same id, the second request won't send any additional Nano.
82
+
83
+ The unit of the `amount` is NANO (which is currently technically 1Mnano &mdash; see [What are Nano's Units](https://nano.org/en/faq#what-are-nano-units-)).
84
+
85
+ Note, there may be a delay in receiving a response due to Proof of Work being done. From the [Nano RPC](https://github.com/nanocurrency/raiblocks/wiki/RPC-protocol#account-create):
86
+
87
+ > Proof of Work is precomputed for one transaction in the background. If it has been a while since your last transaction it will send instantly, the next one will need to wait for Proof of Work to be generated.
88
+
89
+ ### Receiving a payment
90
+
91
+ The simplest way to receive a payment is:
92
+
93
+ ```ruby
94
+ account = Nanook.new.wallet(wallet_id).account(account_id)
95
+ account.receive
96
+
97
+ # or:
98
+
99
+ wallet = Nanook.new.wallet(wallet_id)
100
+ wallet.receive(into: account_id)
101
+ ```
102
+
103
+ The `receive` method when called as above will receive the latest pending payment for an account in a wallet. It will either return a RPC response containing the block if a payment was received, or `false` if there were no pending payments to receive.
104
+
105
+ You can also receive a specific pending block if you know it (you may have discovered it through calling `account.pending` for example):
106
+
107
+ ```ruby
108
+ account = Nanook.new.wallet(wallet_id).account(account_id)
109
+ account.receive(block_id)
110
+
111
+ # or:
112
+
113
+ wallet = Nanook.new.wallet(wallet_id)
114
+ wallet.receive(block_id, into: account_id)
115
+ ```
116
+
117
+ ## All commands
118
+
119
+ ### Wallets
120
+
121
+ #### Create wallet:
122
+
123
+ ```ruby
124
+ Nanook.new.wallet.create
125
+ ```
126
+
127
+ #### Working with a single wallet:
128
+
129
+ ```ruby
130
+ wallet = Nanook.new.wallet(wallet_id)
131
+
132
+ wallet.export
133
+ wallet.locked?
134
+ wallet.unlock(password)
135
+ wallet.change_password(password)
136
+
137
+ wallet.balance
138
+ wallet.balance(account_break_down: true)
139
+ wallet.pay(from: your_account_id, to: recipient_account_id, amount: 0.2, id: unique_id)
140
+ wallet.receive(into: account_id)
141
+ wallet.receive(pending_block_id, into: account_id)
142
+
143
+ wallet.account.create
144
+ wallet.accounts
145
+ wallet.contains?(account_id)
146
+
147
+ wallet.destroy
148
+ ```
149
+ ### Accounts
150
+
151
+ #### Create account:
152
+
153
+ ```ruby
154
+ Nanook.new.wallet(wallet_id).account.create
155
+ ```
156
+
157
+ #### Working with a single account:
158
+
159
+ ```ruby
160
+ account = Nanook.new.wallet(wallet_id).account(account_id)
161
+
162
+ account.exists?
163
+ account.info
164
+ account.ledger
165
+ account.ledger(limit: 10)
166
+ account.history
167
+ account.history(limit: 1)
168
+ account.public_key
169
+ account.delegators
170
+ account.representative
171
+ account.weight
172
+
173
+ account.balance
174
+ account.pay(to: recipient_account_id, amount: 0.2, id: unique_id)
175
+ account.pending
176
+ account.pending(limit: 1)
177
+ account.receive
178
+ account.receive(pending_block_id)
179
+
180
+ account.destroy
181
+ ```
182
+
183
+ #### Working with any account (not necessarily in your wallet):
184
+
185
+ ```ruby
186
+ account = Nanook.new.account(account_id)
187
+
188
+ account.exists?
189
+ account.info
190
+ account.ledger
191
+ account.ledger(limit: 10)
192
+ account.history
193
+ account.history(limit: 1)
194
+ account.public_key
195
+ account.delegators
196
+ account.representative
197
+ account.weight
198
+
199
+ account.balance
200
+ account.pending
201
+ account.pending(limit: 1)
202
+ ```
203
+
204
+ ### Blocks
205
+
206
+ ```ruby
207
+ block = Nanook.new.block(block_id)
208
+
209
+ block.info # Verified blocks in the ledger
210
+ block.info(allow_unchecked: true) # Verified blocks AND unchecked synchronizing blocks
211
+ block.account
212
+ block.chain
213
+ block.chain(limit: 10)
214
+ block.history
215
+ block.history(limit: 10)
216
+ block.republish
217
+ block.republish(sources: 2)
218
+ block.republish(destinations: 2)
219
+ block.pending?
220
+ block.process
221
+ block.successors
222
+ block.successors(limit: 10)
223
+
224
+ block.generate_work
225
+ block.cancel_work
226
+ block.is_valid_work?(work_id)
227
+ ```
228
+
229
+ ### Managing your nano node
230
+
231
+ ```ruby
232
+ node = Nanook.new.node
233
+
234
+ node.block_count
235
+ node.block_count_type
236
+ node.bootstrap_any
237
+ node.bootstrap(address: "::ffff:138.201.94.249", port: 7075)
238
+ node.frontier_count
239
+ node.peers
240
+ node.representatives
241
+ node.stop
242
+ node.version
243
+ ```
244
+
245
+ ### Work peers
246
+
247
+ ```ruby
248
+ work_peers = Nanook.new.work_peers
249
+
250
+ work_peers.add(address: "::ffff:172.17.0.1:7076", port: 7076)
251
+ work_peers.clear
252
+ work_peers.list
253
+ ```
254
+
255
+ ### Keys
256
+
257
+ #### Create private public key pair:
258
+
259
+ ```ruby
260
+ Nanook.new.key.generate
261
+ Nanook.new.key.generate(seed: seed, index: 0)
262
+ ```
263
+
264
+ #### Working with a single key
265
+
266
+ ```ruby
267
+ key = Nanook.new.key(private_key)
268
+
269
+ key.info
270
+ ```
271
+
272
+ ## Nanook Metal
273
+
274
+ You can do any call listed in the [Nano RPC](https://github.com/nanocurrency/raiblocks/wiki/RPC-protocol) directly through the `rpc` method. The first argument should match the `action` of the RPC call, and then all remaining parameters are passed in as arguments.
275
+
276
+ E.g., the [accounts_create command](https://github.com/nanocurrency/raiblocks/wiki/RPC-protocol#accounts-create) can be called like this:
277
+
278
+ ```ruby
279
+ Nano.new.rpc(:accounts_create, wallet: wallet_id, count: 2)
280
+ ```
281
+
282
+ ## Contributing
283
+
284
+ Bug reports and pull requests are welcome. Pull requests with passing tests are even better.
285
+
286
+ To run the test suite:
287
+
288
+ bundle exec rspec spec
289
+
290
+ ## License
291
+
292
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
293
+
294
+
295
+ ## Buy me a nano coffee
296
+
297
+ This library is totally free to use, but feel free to send some nano [my way](https://www.nanode.co/account/xrb_3c3ek3k8135f6e8qtfy8eruk9q3yzmpebes7btzncccdest8ymzhjmnr196j) if you'd like to!
298
+
299
+ xrb_3c3ek3k8135f6e8qtfy8eruk9q3yzmpebes7btzncccdest8ymzhjmnr196j
300
+
301
+ ![alt xrb_3c3ek3k8135f6e8qtfy8eruk9q3yzmpebes7btzncccdest8ymzhjmnr196j](https://raw.githubusercontent.com/lukes/nanook/master/img/qr.png)
302
+
303
+
304
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "nanook"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/img/qr.png ADDED
Binary file
data/lib/nanook.rb ADDED
@@ -0,0 +1,46 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ require 'nanook/account'
5
+ require 'nanook/block'
6
+ require 'nanook/error'
7
+ require 'nanook/key'
8
+ require 'nanook/node'
9
+ require 'nanook/rpc'
10
+ require 'nanook/util'
11
+ require 'nanook/version'
12
+ require 'nanook/wallet_account'
13
+ require 'nanook/wallet'
14
+ require 'nanook/work_peer'
15
+
16
+ class Nanook
17
+
18
+ def initialize(uri=Nanook::Rpc::DEFAULT_URI, timeout:Nanook::Rpc::DEFAULT_URI)
19
+ @rpc = Nanook::Rpc.new(uri, timeout: timeout)
20
+ end
21
+
22
+ def account(account=nil)
23
+ Nanook::Account.new(account, @rpc)
24
+ end
25
+
26
+ def block(block=nil)
27
+ Nanook::Block.new(block, @rpc)
28
+ end
29
+
30
+ def key(key=nil)
31
+ Nanook::Key.new(key, @rpc)
32
+ end
33
+
34
+ def node
35
+ Nanook::Node.new(@rpc)
36
+ end
37
+
38
+ def wallet(wallet=nil)
39
+ Nanook::Wallet.new(wallet, @rpc)
40
+ end
41
+
42
+ def work_peers
43
+ Nanook::WorkPeer.new(@rpc)
44
+ end
45
+
46
+ end
@@ -0,0 +1,74 @@
1
+ class Nanook
2
+ class Account
3
+
4
+ def initialize(account, rpc)
5
+ @account = account
6
+ @rpc = rpc
7
+ end
8
+
9
+ def delegators
10
+ account_required!
11
+ rpc(:delegators)
12
+ end
13
+
14
+ def exists?
15
+ account_required!
16
+ response = rpc(:validate_account_number)
17
+ !response.empty? && response[:valid] == 1
18
+ end
19
+
20
+ def history(limit: 1000)
21
+ account_required!
22
+ rpc(:account_history, count: limit)
23
+ end
24
+
25
+ def public_key
26
+ account_required!
27
+ rpc(:account_key)
28
+ end
29
+
30
+ def representative
31
+ account_required!
32
+ rpc(:account_representative)
33
+ end
34
+
35
+ def balance
36
+ account_required!
37
+ rpc(:account_balance)
38
+ end
39
+
40
+ def info
41
+ account_required!
42
+ rpc(:account_info)
43
+ end
44
+
45
+ def ledger(limit: 1)
46
+ account_required!
47
+ rpc(:ledger, count: limit)
48
+ end
49
+
50
+ def pending(limit: 1000)
51
+ account_required!
52
+ rpc(:pending, count: limit)
53
+ end
54
+
55
+ def weight
56
+ account_required!
57
+ rpc(:account_weight)
58
+ end
59
+
60
+ private
61
+
62
+ def rpc(action, params={})
63
+ p = @account.nil? ? {} : { account: @account }
64
+ @rpc.call(action, p.merge(params))
65
+ end
66
+
67
+ def account_required!
68
+ if @account.nil?
69
+ raise ArgumentError.new("Account must be present")
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,97 @@
1
+ class Nanook
2
+ class Block
3
+
4
+ def initialize(block, rpc)
5
+ @block = block
6
+ @rpc = rpc
7
+ block_required! # All methods expect a block
8
+ end
9
+
10
+ def account
11
+ rpc(:block_account, :hash)
12
+ end
13
+
14
+ def cancel_work
15
+ rpc(:work_cancel, :hash)
16
+ end
17
+
18
+ def chain(limit: 1000)
19
+ rpc(:chain, :block, count: limit)
20
+ end
21
+
22
+ def generate_work
23
+ rpc(:work_generate, :hash)
24
+ end
25
+
26
+ def history(limit: 1000)
27
+ rpc(:history, :hash, count: limit)
28
+ end
29
+
30
+ def info(allow_unchecked: false)
31
+ if allow_unchecked
32
+ # TODO not actually sure what this response looks like when it's not an unchecked block, assuming its blank
33
+ response = rpc(:unchecked_get, :hash)
34
+ return response unless response[:error] == "Block not found"
35
+ # Continue on falling backto checked block
36
+ end
37
+
38
+ response = rpc(:block, :hash)
39
+
40
+ # The contents is a stringified JSON
41
+ if response[:contents]
42
+ response[:contents] = JSON.parse(response[:contents])
43
+ end
44
+
45
+ response
46
+ end
47
+
48
+ def is_valid_work?(work)
49
+ response = rpc(:work_validate, :hash, work: work)
50
+ !response.empty? && response[:valid] == 1
51
+ end
52
+
53
+ def republish(destinations:nil, sources:nil)
54
+ if !destinations.nil? && !sources.nil?
55
+ raise ArgumentError.new("You must provide either destinations or sources but not both")
56
+ end
57
+
58
+ # Add in optional arguments
59
+ params = {}
60
+ params[:destinations] = destinations unless destinations.nil?
61
+ params[:sources] = sources unless sources.nil?
62
+ params[:count] = 1 unless params.empty?
63
+
64
+ rpc(:republish, :hash, params)
65
+ end
66
+
67
+ def pending?
68
+ response = rpc(:pending_exists, :hash)
69
+ !response.empty? && response[:exists] == 1
70
+ end
71
+
72
+ def process
73
+ rpc(:process, :block)
74
+ end
75
+
76
+ def successors(limit: 1000)
77
+ rpc(:successors, :block, count: limit)
78
+ end
79
+
80
+ private
81
+
82
+ # Some RPC calls expect the param that represents the block to be named
83
+ # "hash", and others "block".
84
+ # The param_name argument allows us to specify which it should be for this call.
85
+ def rpc(action, param_name, params={})
86
+ p = @block.nil? ? {} : { param_name.to_sym => @block }
87
+ @rpc.call(action, p.merge(params))
88
+ end
89
+
90
+ def block_required!
91
+ if @block.nil?
92
+ raise ArgumentError.new("Block must be present")
93
+ end
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,4 @@
1
+ class Nanook
2
+ class Error < StandardError
3
+ end
4
+ end
data/lib/nanook/key.rb ADDED
@@ -0,0 +1,38 @@
1
+ class Nanook
2
+ class Key
3
+
4
+ def initialize(key=nil, rpc)
5
+ @key = key
6
+ @rpc = rpc
7
+ end
8
+
9
+ def generate(seed: nil, index: nil)
10
+ if seed.nil? && index.nil?
11
+ rpc(:key_create)
12
+ elsif !seed.nil? && !index.nil?
13
+ rpc(:deterministic_key, seed: seed, index: index)
14
+ else
15
+ raise ArgumentError.new("This method must be called with either seed AND index params given or no params")
16
+ end
17
+ end
18
+
19
+ def info
20
+ key_required!
21
+ rpc(:key_expand)
22
+ end
23
+
24
+ private
25
+
26
+ def rpc(action, params={})
27
+ p = @key.nil? ? {} : { key: @key }
28
+ @rpc.call(action, p.merge(params))
29
+ end
30
+
31
+ def key_required!
32
+ if @key.nil?
33
+ raise ArgumentError.new("Key must be present")
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,51 @@
1
+ class Nanook
2
+ class Node
3
+
4
+ def initialize(rpc)
5
+ @rpc = rpc
6
+ end
7
+
8
+ def block_count
9
+ rpc(:block_count)
10
+ end
11
+
12
+ def block_count_type
13
+ rpc(:block_count_type)
14
+ end
15
+
16
+ def bootstrap(address:, port:)
17
+ rpc(:bootstrap, address: address, port: port)
18
+ end
19
+
20
+ def bootstrap_any
21
+ rpc(:bootstrap_any)
22
+ end
23
+
24
+ def frontier_count
25
+ rpc(:frontier_count)
26
+ end
27
+
28
+ def peers
29
+ rpc(:peers)
30
+ end
31
+
32
+ def representatives
33
+ rpc(:representatives)
34
+ end
35
+
36
+ def stop
37
+ rpc(:stop)
38
+ end
39
+
40
+ def version
41
+ rpc(:version)
42
+ end
43
+
44
+ private
45
+
46
+ def rpc(action, params={})
47
+ @rpc.call(action, params)
48
+ end
49
+
50
+ end
51
+ end
data/lib/nanook/rpc.rb ADDED
@@ -0,0 +1,72 @@
1
+ require 'json'
2
+ require 'symbolized'
3
+
4
+ require 'nanook/error'
5
+
6
+ class Nanook
7
+ class Rpc
8
+
9
+ DEFAULT_URI = "http://localhost:7076"
10
+ DEFAULT_TIMEOUT = 500 # seconds
11
+
12
+ def initialize(uri=DEFAULT_URI, timeout:DEFAULT_TIMEOUT)
13
+ rpc_server = URI(uri)
14
+
15
+ unless ['http', 'https'].include?(rpc_server.scheme)
16
+ raise ArgumentError.new("URI must have http or https in it. Was given: #{uri}")
17
+ end
18
+
19
+ @http = Net::HTTP.new(rpc_server.host, rpc_server.port)
20
+ @http.read_timeout = timeout
21
+ @request = Net::HTTP::Post.new(rpc_server.request_uri, {"user-agent" => "Ruby nanook gem"})
22
+ @request.content_type = "application/json"
23
+ end
24
+
25
+ def call(action, params={})
26
+ # Stringify param values
27
+ params = Hash[params.map {|k, v| [k, v.to_s] }]
28
+
29
+ @request.body = { action: action }.merge(params).to_json
30
+
31
+ response = @http.request(@request)
32
+
33
+ if response.is_a?(Net::HTTPSuccess)
34
+ hash = JSON.parse(response.body)
35
+ process_hash(hash)
36
+ else
37
+ raise Nanook::Error.new("Encountered net/http error #{response.code}: #{response.class.name}")
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # Convert Strings of primitives to primitives
44
+ def process_hash(h)
45
+ new_hash = h.map do |k,v|
46
+ v = if v.is_a?(Array)
47
+ if v[0].is_a?(Hash)
48
+ v.map{|v| process_hash(v)}
49
+ else
50
+ v.map{|v| parse_value(v)}
51
+ end
52
+ elsif v.is_a?(Hash)
53
+ process_hash(v)
54
+ else
55
+ parse_value(v)
56
+ end
57
+
58
+ [k, v]
59
+ end
60
+
61
+ Hash[new_hash].to_symbolized_hash
62
+ end
63
+
64
+ def parse_value(v)
65
+ return v.to_i if v.match(/^\d+\Z/)
66
+ return true if v == "true"
67
+ return false if v == "false"
68
+ v
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,9 @@
1
+ class Nanook
2
+ class Util
3
+
4
+ def self.NANO_to_raw(nano)
5
+ nano * (10**30)
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ class Nanook
2
+ VERSION = "0.6.0"
3
+ end
@@ -0,0 +1,92 @@
1
+ class Nanook
2
+ class Wallet
3
+
4
+ def initialize(wallet, rpc)
5
+ @wallet = wallet
6
+ @rpc = rpc
7
+ end
8
+
9
+ def account(account=nil)
10
+ Nanook::WalletAccount.new(@wallet, account, @rpc)
11
+ end
12
+
13
+ def accounts
14
+ wallet_required!
15
+ rpc(:account_list)
16
+ end
17
+
18
+ def balance(account_break_down: false)
19
+ wallet_required!
20
+ if account_break_down
21
+ rpc(:wallet_balances)
22
+ else
23
+ rpc(:wallet_balance_total)
24
+ end
25
+ end
26
+
27
+ def create
28
+ rpc(:wallet_create)
29
+ end
30
+
31
+ def destroy
32
+ wallet_required!
33
+ rpc(:wallet_destroy)
34
+ end
35
+
36
+ def export
37
+ wallet_required!
38
+ rpc(:wallet_export)
39
+ end
40
+
41
+ def contains?(account)
42
+ wallet_required!
43
+ response = rpc(:wallet_contains, account: account)
44
+ !response.empty? && response[:exists] == 1
45
+ end
46
+
47
+ def pay(from:, to:, amount:, id:)
48
+ wallet_required!
49
+ account(from).pay(to: to, amount: amount, id: id)
50
+ end
51
+
52
+ def receive(block=nil, into:)
53
+ wallet_required!
54
+ account(into).receive(block)
55
+ end
56
+
57
+ def locked?
58
+ wallet_required!
59
+ response = rpc(:wallet_locked)
60
+ !response.empty? && response[:locked] != 0
61
+ end
62
+
63
+ def unlock(password)
64
+ wallet_required!
65
+ rpc(:password_enter, password: password)
66
+ end
67
+
68
+ def change_password(password)
69
+ wallet_required!
70
+ rpc(:password_change, password: password)
71
+ end
72
+
73
+ def all
74
+ wallet_required!
75
+ rpc(:account_list)
76
+ end
77
+
78
+ private
79
+
80
+ def rpc(action, params={})
81
+ p = @wallet.nil? ? {} : { wallet: @wallet }
82
+ @rpc.call(action, p.merge(params))
83
+ end
84
+
85
+ def wallet_required!
86
+ if @wallet.nil?
87
+ raise ArgumentError.new("Wallet must be present")
88
+ end
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,104 @@
1
+ class Nanook
2
+ class WalletAccount
3
+
4
+ def initialize(wallet, account, rpc)
5
+ @wallet = wallet
6
+ @account = account
7
+ @rpc = rpc
8
+
9
+ # An object to delegate account methods that don't
10
+ # expect a wallet param in the RPC call, to allow this
11
+ # class to support all methods that can be called on Nanook::Account
12
+ @nanook_account_instance = Nanook::Account.new(account, rpc)
13
+ end
14
+
15
+ def create
16
+ wallet_required!
17
+ rpc(:account_create)
18
+ end
19
+
20
+ def destroy
21
+ wallet_required!
22
+ account_required!
23
+ rpc(:account_remove)
24
+ end
25
+
26
+ def pay(to:, amount:, id:)
27
+ wallet_required!
28
+ account_required!
29
+
30
+ raw = Nanook::Util.NANO_to_raw(amount)
31
+
32
+ # account is called source, so don't use the normal rpc method
33
+ p = {
34
+ wallet: @wallet,
35
+ source: @account,
36
+ destination: to,
37
+ amount: raw,
38
+ id: id
39
+ }
40
+
41
+ @rpc.call(:send, p)
42
+ end
43
+
44
+ def receive(block=nil)
45
+ wallet_required!
46
+ account_required!
47
+
48
+ if block.nil?
49
+ _receive_without_block
50
+ else
51
+ _receive_with_block(block)
52
+ end
53
+ end
54
+
55
+ # Any method of Nanook::Account can be called on this class too
56
+ def method_missing(m, *args, &block)
57
+ if @nanook_account_instance.respond_to?(m)
58
+ @nanook_account_instance.send(m, *args, &block)
59
+ else
60
+ super(m, *args, &block)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def _receive_without_block
67
+ # Discover the first pending block
68
+ pending_blocks = @rpc.call(:pending, { account: @account, count: 1 })
69
+
70
+ if pending_blocks[:blocks].empty?
71
+ return false
72
+ end
73
+
74
+ # Then call receive_with_block as normal
75
+ block = pending_blocks[:blocks][0]
76
+ _receive_with_block(block)
77
+ end
78
+
79
+ def _receive_with_block(block)
80
+ rpc(:receive, block: block)
81
+ end
82
+
83
+ def rpc(action, params={})
84
+ p = {}
85
+ p[:wallet] = @wallet unless @wallet.nil?
86
+ p[:account] = @account unless @account.nil?
87
+
88
+ @rpc.call(action, p.merge(params))
89
+ end
90
+
91
+ def wallet_required!
92
+ if @wallet.nil?
93
+ raise ArgumentError.new("Wallet must be present")
94
+ end
95
+ end
96
+
97
+ def account_required!
98
+ if @account.nil?
99
+ raise ArgumentError.new("Account must be present")
100
+ end
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,27 @@
1
+ class Nanook
2
+ class WorkPeer
3
+
4
+ def initialize(rpc)
5
+ @rpc = rpc
6
+ end
7
+
8
+ def add(address:, port:)
9
+ rpc(:work_peer_add, address: address, port: port)
10
+ end
11
+
12
+ def clear
13
+ rpc(:work_peers_clear)
14
+ end
15
+
16
+ def list
17
+ rpc(:work_peers)
18
+ end
19
+
20
+ private
21
+
22
+ def rpc(action, params={})
23
+ @rpc.call(action, params)
24
+ end
25
+
26
+ end
27
+ end
data/nanook.gemspec ADDED
@@ -0,0 +1,30 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "nanook/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "nanook"
8
+ spec.version = Nanook::VERSION
9
+ spec.authors = ["Luke Duncalfe"]
10
+ spec.email = ["lduncalfe@eml.cc"]
11
+
12
+ spec.summary = "Ruby library for managing a nano currency node, including making and receiving payments, using the nano RPC protocol"
13
+ spec.description = "Ruby library for managing a nano currency node, including making and receiving payments, using the nano RPC protocol"
14
+ spec.homepage = "https://github.com/lukes/nanook"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.16"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.2"
27
+ spec.add_development_dependency "webmock", "~> 3.3"
28
+
29
+ spec.add_dependency "symbolized"
30
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nanook
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.0
5
+ platform: ruby
6
+ authors:
7
+ - Luke Duncalfe
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-03-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: symbolized
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Ruby library for managing a nano currency node, including making and
84
+ receiving payments, using the nano RPC protocol
85
+ email:
86
+ - lduncalfe@eml.cc
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".circleci/config.yml"
92
+ - ".gitignore"
93
+ - Gemfile
94
+ - Gemfile.lock
95
+ - LICENSE.txt
96
+ - README.md
97
+ - Rakefile
98
+ - bin/console
99
+ - bin/setup
100
+ - img/qr.png
101
+ - lib/nanook.rb
102
+ - lib/nanook/account.rb
103
+ - lib/nanook/block.rb
104
+ - lib/nanook/error.rb
105
+ - lib/nanook/key.rb
106
+ - lib/nanook/node.rb
107
+ - lib/nanook/rpc.rb
108
+ - lib/nanook/util.rb
109
+ - lib/nanook/version.rb
110
+ - lib/nanook/wallet.rb
111
+ - lib/nanook/wallet_account.rb
112
+ - lib/nanook/work_peer.rb
113
+ - nanook.gemspec
114
+ homepage: https://github.com/lukes/nanook
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 2.2.2
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Ruby library for managing a nano currency node, including making and receiving
138
+ payments, using the nano RPC protocol
139
+ test_files: []