nanook 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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: []