nano_rpc 0.1.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
+ SHA256:
3
+ metadata.gz: da3e826d03d97a0b481735d2c930a9f2dfd098167ae263c6a91b2b7da55dd607
4
+ data.tar.gz: a22e93555b44d86f5b53cb07ca13d6bc16a38f6f1d9025051655604d146b16a3
5
+ SHA512:
6
+ metadata.gz: 4ebe96119c1db5db4e4ac8d1a75a0dab74717d0fa718062fda33651820113b2021d5da2994fa7508cd67281c19e673d1b26229504720cef3fc77b46905686302
7
+ data.tar.gz: 461f6bdd4eb3db88ca94c2781519616e58bb412e44ab407f6abc76a381b4faa55f0741e054f7604829b3958753f7592e5d2d6f82dc6c716854bf5355dc8a864e
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ .DS_Store
2
+ /.bundle/
3
+ /.yardoc
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .rspec_status
11
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.5.0
3
+ Exclude:
4
+ - 'bin/*'
5
+ DisplayCopNames: true
6
+ Style/ClassAndModuleChildren:
7
+ EnforcedStyle: compact
8
+ Style/EmptyMethod:
9
+ EnforcedStyle: expanded
10
+ Layout/EmptyLineAfterMagicComment:
11
+ Enabled: false
12
+ Documentation:
13
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ source 'https://rubygems.org'
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Justin Craig-Kuhn
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,137 @@
1
+ # Nano RPC
2
+
3
+ An RPC wrapper for Nano (the digital currency) written in Ruby. It connects to an individual Nano node that you control. There's a client object you can use to make explicit RPC calls as well as proxy objects with logically grouped helper methods ([Wiki](https://github.com/jcraigk/ruby_nano_rpc/wiki/Proxy-Object-Reference)).
4
+
5
+ To run a Nano node locally, see [Nano Docker Docs](https://github.com/clemahieu/raiblocks/wiki/Docker-node).
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'nano_rpc'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install nano_rpc
22
+
23
+ ## Usage
24
+
25
+ There are two ways to use this gem. You can make direct calls to the RPC client using Ruby hashes or you can use proxy objects for terser code.
26
+
27
+ ### Raw RPC Calls
28
+
29
+ The RCP client exposes raw Remote Procedure Call methods according to the [Nano RPC Docs](https://github.com/clemahieu/raiblocks/wiki/RPC-protocol).
30
+
31
+ Every method requires an `action`, which is passed as the first argument to `call`. Depending on the action, there may be additional required or optional parameters that are passed as an options hash.
32
+
33
+ First setup the client:
34
+
35
+ ```ruby
36
+ # Connect to localhost:7076
37
+ client = Nano.client
38
+
39
+ # Connect to custom host
40
+ client = Nano::Client.new(host: 'mynanonode', port: 1234)
41
+ ```
42
+
43
+ Then make a `call`, passing the action and data:
44
+
45
+ ```ruby
46
+ client.call(:account_balance, account: 'xrb_someaddress1234')
47
+ # => {"balance"=>100, "pending"=>0}
48
+ ````
49
+
50
+ Response data are provided as [Hashie](https://github.com/intridea/hashie) objects with integer coercion, indifferent access, and method access.
51
+
52
+ ```ruby
53
+ data = client.call(:account_balance, account: 'xrb_someaddress1234')
54
+ # => {"balance"=>100, "pending"=>0}
55
+ data.balance
56
+ # => 100
57
+ data[:balance]
58
+ # => 100
59
+ data['balance']
60
+ # => 100
61
+ ````
62
+
63
+ ### Proxy Objects
64
+
65
+ Proxy objects are provided to ease interaction with the API by providing logically grouped helper methods. Here we do not strictly follow the grouping as expressed in the [Nano RPC Docs](https://github.com/clemahieu/raiblocks/wiki/RPC-protocol). Instead, the following objects are provided:
66
+
67
+ ```ruby
68
+ Nano::Account # { account: 'xrb_address12345' }
69
+ Nano::Accounts # { accounts: %w[xrb_address12345 xrb_address67890] }
70
+ Nano::Node
71
+ Nano::Wallet # { wallet: 'F3093AB' }
72
+ ```
73
+
74
+ `Account`, `Accounts`, and `Wallet` each require a single parameter to be passed during initialization. This parameter is persisted for subsequent calls. All RPC methods are provided directly as methods.
75
+
76
+ ```ruby
77
+ account = Nano::Account.new('xrb_someaddress1234')
78
+
79
+ account.account_balance
80
+ # => {"balance"=>100, "pending"=>0}
81
+ account.account_balance.balance
82
+ # 100
83
+ ```
84
+
85
+ You can ask an object what raw RPC methods it provides using `proxy_methods`:
86
+
87
+ ```ruby
88
+ account.proxy_methods
89
+ # => [:account_balance, :account_block_count, :account_create, ...]
90
+ ```
91
+
92
+ There are also helper methods for terser code:
93
+
94
+ ```ruby
95
+ account = Nano::Account.new('xrb_someaddress1234')
96
+ account.balance
97
+ # 100
98
+ account.pending_balance
99
+ # 0
100
+ ```
101
+
102
+ You can ask an object what helper methods it provides using `helper_methods` (coming soon):
103
+
104
+ ```ruby
105
+ account.helper_methods
106
+ # => [:balance, :pending_balance, ...]
107
+ ```
108
+
109
+ `Node` methods are provided at both the instance and class levels:
110
+
111
+ ```ruby
112
+ Nano::Node.version
113
+ # => {"rpc_version"=>1, "store_version"=>10, "node_vendor"=>"RaiBlocks 9.0"}
114
+
115
+ node = Nano::Node.new
116
+ version.rpc_version
117
+ # => 1
118
+ node.peers
119
+ # => {"peers"=>{"[::ffff:2.80.5.202]:64317"=>"5", "[::ffff:2.249.74.58]:7075"=>"5", "[::ffff:5.9.31.82]:7077"=>"4", ... }
120
+ node.available_supply
121
+ # => {"available"=>132596127030666124778600855847014518457}
122
+ node.block_count.unchecked
123
+ # => 4868605
124
+ ```
125
+
126
+ To connect to a custom node, instantiate a client and pass it into the proxy object as `client`:
127
+
128
+ ```ruby
129
+ client = Nano::Client.new(host: 'mynanonode', port: 1234)
130
+ account = Nano::Account.new('xrb_someaddress1234', client: client)
131
+ ```
132
+
133
+ For a more comprehensive guide, see the [Wiki](https://github.com/jcraigk/ruby_nano_rpc/wiki/Proxy-Object-Reference).
134
+
135
+ ## License
136
+
137
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'nano_rpc'
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
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+ require 'rest-client'
3
+ require 'json'
4
+
5
+ module Nano
6
+ def self.client
7
+ @client ||= Client.new
8
+ end
9
+
10
+ class Client
11
+ attr_accessor :host, :port
12
+
13
+ def initialize(host: 'localhost', port: 7076)
14
+ @host = host
15
+ @port = port
16
+ end
17
+
18
+ def call(action, params = {})
19
+ args = { action: action }
20
+ args.merge!(params) if params.is_a?(Hash)
21
+ post(args)
22
+ end
23
+
24
+ private
25
+
26
+ def post(params)
27
+ response = rest_client_post(url, params)
28
+ ensure_status_success!(response)
29
+
30
+ data = Nano::Response.new(JSON[response.body])
31
+ ensure_valid_response!(data)
32
+
33
+ data
34
+ end
35
+
36
+ def rest_client_post(url, params)
37
+ RestClient.post(url, params.to_json)
38
+ rescue Errno::ECONNREFUSED
39
+ raise Nano::NodeConnectionFailure,
40
+ "Node connection failure at #{url}"
41
+ end
42
+
43
+ def url
44
+ "http://#{host}:#{port}"
45
+ end
46
+
47
+ def ensure_status_success!(response)
48
+ return if response.code == 200
49
+ raise Nano::BadRequest,
50
+ "Error response from node: #{JSON[response.body]}"
51
+ end
52
+
53
+ def ensure_valid_response!(data)
54
+ return unless data['error']
55
+ raise Nano::InvalidRequest,
56
+ "Invalid request: #{data['error']}"
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module Nano
3
+ class Error < StandardError; end
4
+
5
+ class NodeConnectionFailure < Error; end
6
+ class BadRequest < Error; end
7
+ class InvalidRequest < Error; end
8
+ class InvalidParameterType < Error; end
9
+ class ForbiddenParameter < Error; end
10
+ class MissingParameters < Error; end
11
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+ module Nano::AccountProxyHelper
3
+ def balance
4
+ account_balance.balance
5
+ end
6
+
7
+ def block_count
8
+ account_block_count.block_count
9
+ end
10
+
11
+ def create(wallet:, work:)
12
+ account_create(wallet: wallet, work: work).account
13
+ end
14
+
15
+ def history(count:)
16
+ account_history(count: count).history
17
+ end
18
+
19
+ def info
20
+ account_info
21
+ end
22
+
23
+ def key
24
+ account_key.key
25
+ end
26
+
27
+ def list
28
+ account_list.accounts
29
+ end
30
+
31
+ def move(wallet:, source:, accounts:)
32
+ account_move(wallet: wallet, source: source, accounts: accounts).moved == 1
33
+ end
34
+
35
+ def wallet_work(wallet:)
36
+ work_get(wallet: wallet).work
37
+ end
38
+
39
+ def wallet_work_set(wallet:, work:)
40
+ work_set(wallet: wallet, work: work).work[:success] == ''
41
+ end
42
+
43
+ def pending_balance
44
+ account_balance.pending
45
+ end
46
+ alias balance_pending pending_balance
47
+
48
+ def pending_blocks(count:, threshold: nil, exists: nil)
49
+ pending(count: count, threshold: threshold, exists: exists).locks
50
+ end
51
+ alias blocks_pending pending_blocks
52
+
53
+ def remove(wallet:)
54
+ account_remove(wallet: wallet).removed == 1
55
+ end
56
+
57
+ def representative
58
+ account_representative.representative
59
+ end
60
+
61
+ def representative_set(wallet:, representative:)
62
+ account_representative_set(
63
+ wallet: wallet, representative: representative
64
+ ).set == 1
65
+ end
66
+
67
+ def weight
68
+ account_weight.weight
69
+ end
70
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+ module Nano::AccountsProxyHelper
3
+ def balances
4
+ accounts_balances.balances
5
+ end
6
+
7
+ def create(wallet:, count:, work: nil)
8
+ accounts_create(wallet: wallet, count: count, work: work).accounts
9
+ end
10
+
11
+ def frontiers
12
+ accounts_frontiers.frontiers
13
+ end
14
+
15
+ def pending(count:, threshold: nil, source: nil)
16
+ accounts_pending(count: count, threshold: threshold, source: source).blocks
17
+ end
18
+ alias pending_blocks pending
19
+
20
+ # Array-like access for Nano::Account
21
+ def [](idx)
22
+ return unless @addresses[idx]
23
+ @account_objects ||= []
24
+ @account_objects[idx] ||= Nano::Account.new(@addresses[idx])
25
+ end
26
+
27
+ def <<(val)
28
+ @addresses << val
29
+ end
30
+
31
+ def each(&_block)
32
+ @addresses.each do |address|
33
+ yield Nano::Account.new(address)
34
+ end
35
+ end
36
+
37
+ def first
38
+ self[0]
39
+ end
40
+
41
+ def second
42
+ self[1]
43
+ end
44
+
45
+ def third
46
+ self[2]
47
+ end
48
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ module Nano::NodeProxyHelper
3
+ def account_containing_block(hash)
4
+ block_account(hash: hash).account
5
+ end
6
+
7
+ def available
8
+ available_supply.available
9
+ end
10
+
11
+ def krai_from(amount:)
12
+ krai_from_raw(amount: amount).amount
13
+ end
14
+
15
+ def krai_to(amount:)
16
+ krai_to_raw(amount: amount).amount
17
+ end
18
+
19
+ def mrai_from(amount:)
20
+ mrai_from_raw(amount: amount).amount
21
+ end
22
+
23
+ def mrai_to(amount:)
24
+ mrai_to_raw(amount: amount).amount
25
+ end
26
+
27
+ def pending_exists?(hash:)
28
+ pending_exists(hash: hash).exists == 1
29
+ end
30
+
31
+ def rai_from(amount:)
32
+ rai_from_raw(amount: amount).amount
33
+ end
34
+
35
+ def rai_to(amount:)
36
+ rai_to_raw(amount: amount).amount
37
+ end
38
+
39
+ def work_valid?(work:, hash:)
40
+ work_validate(work: work, hash: hash).valid == 1
41
+ end
42
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+ module Nano::WalletProxyHelper
3
+ def account_work(account:)
4
+ work_get.work(account: account).work
5
+ end
6
+
7
+ def add(key:, work: nil)
8
+ wallet_add(key: key, work: work).account
9
+ end
10
+
11
+ def balance
12
+ wallet_balance_total.balance
13
+ end
14
+
15
+ def balances(threshold: nil)
16
+ wallet_balances(threshold: threshold).balances
17
+ end
18
+
19
+ def begin_payment
20
+ payment_begin.account
21
+ end
22
+
23
+ def change_password(new_password:)
24
+ password_change(password: new_password).changed == 1
25
+ end
26
+
27
+ def change_seed(new_seed:)
28
+ wallet_change_seed(seed: new_seed)[:success] == ''
29
+ end
30
+
31
+ def contains?(account:)
32
+ wallet_contains(account: account).exists == 1
33
+ end
34
+
35
+ def create
36
+ wallet_create.wallet
37
+ end
38
+
39
+ def destroy
40
+ wallet_destroy
41
+ end
42
+
43
+ def enter_password(password)
44
+ password_enter(password: password).valid == 1
45
+ end
46
+
47
+ def export
48
+ JSON[wallet_export.json]
49
+ end
50
+
51
+ def frontiers
52
+ wallet_frontiers.frontiers
53
+ end
54
+
55
+ def init_payment
56
+ payment_init.status == 'Ready'
57
+ end
58
+
59
+ def locked?
60
+ wallet_locked.locked == 1
61
+ end
62
+
63
+ def password_valid?(password:)
64
+ password_valid(password: password).valid == 1
65
+ end
66
+
67
+ def pending_balance
68
+ wallet_balance_total.pending
69
+ end
70
+
71
+ def pending_blocks(count:, threshold: nil, source: nil)
72
+ wallet_pending(count: count, threshold: threshold, source: source).blocks
73
+ end
74
+
75
+ def receive_block(account:, block:)
76
+ receive.block(account: account, block: block).block
77
+ end
78
+
79
+ def representative
80
+ wallet_representative.representative
81
+ end
82
+
83
+ def republish(count:)
84
+ wallet_republish(count: count).blocks
85
+ end
86
+
87
+ def account_work_set(account:, work:)
88
+ work_set(account: account, work: work).work[:success] == ''
89
+ end
90
+ alias set_account_work account_work_set
91
+
92
+ def representative_set(representative:)
93
+ wallet_representative_set(representative: representative).set == 1
94
+ end
95
+ alias set_representative representative_set
96
+
97
+ def send_tx(source:, destination:, amount:)
98
+ rai_send(source: source, destination: destination, amount: amount).block
99
+ end
100
+ alias send_transaction send_tx
101
+
102
+ def work
103
+ wallet_work_get.works
104
+ end
105
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ class Nano::Account
3
+ include Nano::Proxy
4
+ include Nano::AccountProxyHelper
5
+
6
+ attr_accessor :address
7
+
8
+ def initialize(address = nil, opts = {})
9
+ unless address.is_a?(String)
10
+ raise Nano::MissingParameters,
11
+ 'Missing argument: address (str)'
12
+ end
13
+
14
+ @address = address
15
+ @client = opts[:client] || Nano.client
16
+ end
17
+
18
+ proxy_params account: :address
19
+
20
+ proxy_method :account_balance
21
+ proxy_method :account_block_count
22
+ proxy_method :account_info
23
+ proxy_method :account_create, required: %i[wallet], optional: %i[work]
24
+ proxy_method :account_history, required: %i[count]
25
+ proxy_method :account_list
26
+ proxy_method :account_move, required: %i[wallet source accounts]
27
+ proxy_method :account_key
28
+ proxy_method :account_remove, required: %i[wallet]
29
+ proxy_method :account_representative
30
+ proxy_method :account_representative_set, required: %i[wallet representative]
31
+ proxy_method :account_weight
32
+ proxy_method :delegators
33
+ proxy_method :delegators_count
34
+ proxy_method :frontiers, required: %i[count]
35
+ proxy_method :frontier_count
36
+ proxy_method :ledger,
37
+ required: %i[count],
38
+ optional: %i[representative weight pending sorting]
39
+ proxy_method :validate_account_number
40
+ proxy_method :pending, required: %i[count], optional: %i[threshold exists]
41
+ proxy_method :payment_wait, required: %i[amount timeout]
42
+ proxy_method :work_get
43
+ proxy_method :work_set
44
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ class Nano::Accounts
3
+ include Nano::Proxy
4
+ include Nano::AccountsProxyHelper
5
+
6
+ attr_accessor :addresses
7
+
8
+ def initialize(addresses = nil, client = nil)
9
+ unless addresses.is_a?(Array)
10
+ raise Nano::MissingParameters,
11
+ 'Missing argument: addresses (str[])'
12
+ end
13
+
14
+ @addresses = addresses
15
+ @client = client || Nano.client
16
+ end
17
+
18
+ proxy_params accounts: :addresses
19
+
20
+ proxy_method :accounts_balances
21
+ proxy_method :accounts_create, required: %i[wallet count], optional: %i[work]
22
+ proxy_method :accounts_frontiers
23
+ proxy_method :accounts_pending,
24
+ required: %i[count], optional: %i[threshold source]
25
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+ class Nano::Node
3
+ include Nano::Proxy
4
+ include Nano::NodeProxyHelper
5
+
6
+ proxy_method :available_supply
7
+ proxy_method :block, required: %i[hash]
8
+ proxy_method :block_account, required: %i[hash]
9
+ proxy_method :block_count
10
+ proxy_method :block_count_type
11
+ proxy_method :block_create,
12
+ required: %i[type key representative source], optional: %i[work]
13
+ proxy_method :blocks, required: %i[hashes]
14
+ proxy_method :blocks_info,
15
+ required: %i[hashes], optional: %i[pending source]
16
+ proxy_method :bootstrap, required: %i[address port]
17
+ proxy_method :bootstrap_any
18
+ proxy_method :chain, required: %i[block count]
19
+ proxy_method :deterministic_key, required: %i[seed index]
20
+ proxy_method :history, required: %i[hash count]
21
+ proxy_method :keepalive, required: %i[address port]
22
+ proxy_method :key_create
23
+ proxy_method :key_expand, required: %i[key]
24
+ proxy_method :krai_from_raw, required: %i[amount]
25
+ proxy_method :krai_to_raw, required: %i[amount]
26
+ proxy_method :mrai_from_raw, required: %i[amount]
27
+ proxy_method :mrai_to_raw, required: %i[amount]
28
+ proxy_method :payment_wait, required: %i[account amount timeout]
29
+ proxy_method :peers
30
+ proxy_method :pending_exists, required: %i[hash]
31
+ proxy_method :process, required: %i[block]
32
+ proxy_method :rai_from_raw, required: %i[amount]
33
+ proxy_method :rai_to_raw, required: %i[amount]
34
+ proxy_method :receive_minimum
35
+ proxy_method :receive_minimum_set, required: %i[amount]
36
+ proxy_method :representatives
37
+ proxy_method :republish,
38
+ required: %i[hash],
39
+ optional: %i[count sources destinations]
40
+ proxy_method :search_pending, required: %i[wallet]
41
+ proxy_method :search_pending_all
42
+ proxy_method :stop
43
+ proxy_method :successors, required: %i[block count]
44
+ proxy_method :unchecked, required: %i[count]
45
+ proxy_method :unchecked_clear
46
+ proxy_method :unchecked_get, required: %i[hash]
47
+ proxy_method :unchecked_keys, required: %i[key count]
48
+ proxy_method :version
49
+ proxy_method :work_cancel, required: %i[hash]
50
+ proxy_method :work_generate, required: %i[hash]
51
+ proxy_method :work_peer_add, required: %i[address port]
52
+ proxy_method :work_peers
53
+ proxy_method :work_peers_clear
54
+ proxy_method :work_validate, required: %i[work hash]
55
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+ class Nano::Wallet
3
+ include Nano::Proxy
4
+ include Nano::WalletProxyHelper
5
+
6
+ attr_accessor :seed
7
+
8
+ def initialize(wallet_seed = nil, opts = {})
9
+ unless wallet_seed.is_a?(String)
10
+ raise Nano::MissingParameters,
11
+ 'Missing argument: address (str)'
12
+ end
13
+
14
+ @seed = wallet_seed
15
+ @client = opts[:client] || Nano.client
16
+ end
17
+
18
+ proxy_params wallet: :seed
19
+
20
+ proxy_method :password_change, required: %i[password]
21
+ proxy_method :password_enter, required: %i[password]
22
+ proxy_method :password_valid
23
+ proxy_method :payment_begin
24
+ proxy_method :payment_init
25
+ proxy_method :payment_end, required: %i[account]
26
+ proxy_method :receive, required: %i[account block]
27
+ proxy_method :send, required: %i[wallet source destination amount]
28
+ proxy_method :search_pending
29
+ proxy_method :wallet_add, required: %i[key], optional: %i[work]
30
+ proxy_method :wallet_balance_total
31
+ proxy_method :wallet_balances, optional: %i[threshold]
32
+ proxy_method :wallet_change_seed, required: %i[seed]
33
+ proxy_method :wallet_contains, required: %i[account]
34
+ proxy_method :wallet_create
35
+ proxy_method :wallet_destroy
36
+ proxy_method :wallet_export
37
+ proxy_method :wallet_frontiers
38
+ proxy_method :wallet_locked
39
+ proxy_method :wallet_pending,
40
+ required: %i[count], optional: %i[threshold source]
41
+ proxy_method :wallet_representative
42
+ proxy_method :wallet_representative_set, required: %i[representative]
43
+ proxy_method :wallet_republish, required: %i[count]
44
+ proxy_method :wallet_work_get
45
+ proxy_method :work_get
46
+ proxy_method :work_set
47
+ end
data/lib/nano/proxy.rb ADDED
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+ module Nano::Proxy
3
+ attr_accessor :client
4
+
5
+ def initialize(opts = {})
6
+ @client = opts[:client] || Nano.client
7
+ end
8
+
9
+ def self.included(base)
10
+ base.extend(ClassMethods)
11
+ end
12
+ module ClassMethods
13
+ attr_reader :proxy_method_def, :proxy_param_def
14
+
15
+ def proxy_params(param_def = nil)
16
+ @proxy_param_def = param_def
17
+ end
18
+
19
+ def proxy_method(name, signature = nil)
20
+ @proxy_method_def ||= {}
21
+ @proxy_method_def[name] = signature
22
+ end
23
+
24
+ def proxy_methods
25
+ proxy_method_def.keys.sort
26
+ end
27
+
28
+ def methods
29
+ (super + proxy_methods).sort
30
+ end
31
+
32
+ def define_proxy_method(m, singleton = false)
33
+ send(
34
+ singleton ? :define_singleton_method : :define_method,
35
+ method_alias(m)
36
+ ) do |opts = {}|
37
+ params = Nano::ProxyContext.new(
38
+ singleton ? self : self.class, m, opts
39
+ ).populate_params(singleton ? nil : base_params)
40
+ data = Nano.client.call(m, params)
41
+ data.is_a?(Hash) && data.keys.map(&:to_s) == [m.to_s] ? data[m] : data
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ # Nano `send` action is also the method caller in Ruby ;)
48
+ def method_alias(m)
49
+ m == :send ? :tx_send : m
50
+ end
51
+
52
+ def method_missing(m, *args, &_block)
53
+ return super unless valid_method?(m)
54
+ define_proxy_method(m, true)
55
+ send(m, args.first)
56
+ end
57
+
58
+ def respond_to_missing?(m, include_private = false)
59
+ valid_method?(m) || super
60
+ end
61
+
62
+ def valid_method?(m)
63
+ proxy_param_def.nil? && methods.include?(m)
64
+ end
65
+
66
+ def proxy_context(m)
67
+ @proxy_context ||= {}
68
+ @proxy_context[m] ||= Nano::ProxyContext.new(self, m)
69
+ end
70
+ end
71
+
72
+ def proxy_methods
73
+ self.class.proxy_methods
74
+ end
75
+
76
+ def methods
77
+ (super + proxy_methods).sort
78
+ end
79
+
80
+ private
81
+
82
+ def base_params
83
+ return if self.class.proxy_param_def.nil?
84
+ self.class
85
+ .proxy_param_def
86
+ .each_with_object({}) do |(k, v), params|
87
+ params[k] ||= send(v)
88
+ end
89
+ end
90
+
91
+ def method_missing(m, *args, &_block)
92
+ return super unless methods.include?(m)
93
+ self.class.define_proxy_method(m)
94
+ send(m, args.first)
95
+ end
96
+
97
+ def respond_to_missing?(m, include_private = false)
98
+ methods.include?(m) || super
99
+ end
100
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+ class Nano::ProxyContext
3
+ def initialize(klass, m, opts = {})
4
+ @klass = klass
5
+ @method_def = klass.proxy_method_def
6
+ @param_def = klass.proxy_param_def
7
+ @m = m
8
+ @opts = opts
9
+ end
10
+
11
+ def valid_proxy_method?
12
+ rpc_action?
13
+ end
14
+
15
+ def populate_params(params)
16
+ opts = validate_opts!
17
+ opts.merge!(params) if params
18
+ validate_params!
19
+ opts.delete_if { |_k, v| v.nil? }
20
+ opts
21
+ end
22
+
23
+ def validate_opts!
24
+ return @opts if @opts.is_a?(Hash)
25
+ return {} if @opts.nil?
26
+ raise Nano::InvalidParameterType,
27
+ 'You must pass a hash to an action method'
28
+ end
29
+
30
+ def validate_params!
31
+ ensure_required_params!
32
+ ensure_no_forbidden_params!
33
+ end
34
+
35
+ def ensure_required_params!
36
+ missing_params = required_params - opts_keys
37
+ return unless missing_params.any?
38
+ raise Nano::MissingParameters,
39
+ "Missing required parameter(s): #{missing_params.join(', ')}"
40
+ end
41
+
42
+ def ensure_no_forbidden_params!
43
+ forbidden_params = base_param_keys + opts_keys - allowed_params
44
+ return unless forbidden_params.any?
45
+ raise Nano::ForbiddenParameter,
46
+ "Forbidden parameter(s) passed: #{forbidden_params.join(', ')}"
47
+ end
48
+
49
+ private
50
+
51
+ def opts_keys
52
+ @opts.nil? ? [] : @opts.keys
53
+ end
54
+
55
+ def allowed_params
56
+ base_param_keys + required_params + optional_params
57
+ end
58
+
59
+ def required_params
60
+ return [] unless @method_def && @method_def[@m]
61
+ @method_def[@m][:required]
62
+ end
63
+
64
+ def optional_params
65
+ return [] unless @method_def && @method_def[@m]
66
+ @method_def[@m][:optional]
67
+ end
68
+
69
+ def base_param_keys
70
+ @param_def.is_a?(Hash) ? @param_def.keys : []
71
+ end
72
+
73
+ def method_expansion
74
+ "#{action_prefix}#{@m}"
75
+ end
76
+
77
+ def action_prefix
78
+ @klass.name.split('::').last.downcase + '_'
79
+ end
80
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ require 'hashie'
3
+
4
+ class Nano::Response < Hash
5
+ include ::Hashie::Extensions::MergeInitializer
6
+ include ::Hashie::Extensions::IndifferentAccess
7
+ include ::Hashie::Extensions::MethodAccess
8
+
9
+ def initialize(hash = {})
10
+ super
11
+ coerce_values
12
+ end
13
+
14
+ private
15
+
16
+ def coerce_values
17
+ merge!(self) { |_k, v| to_f_or_i_or_s(v) }
18
+ end
19
+
20
+ def to_f_or_i_or_s(v)
21
+ (float = Float(v)) && (float % 1.0).zero? ? float.to_i : float
22
+ rescue ArgumentError, TypeErrror
23
+ v
24
+ end
25
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Nano
3
+ VERSION = '0.1.0'
4
+ end
data/lib/nano_rpc.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ require 'nano/version'
3
+ require 'nano/client'
4
+ require 'nano/errors'
5
+ require 'nano/proxy'
6
+ require 'nano/proxy_context'
7
+ require 'nano/response'
8
+ require 'nano/helpers/account_proxy_helper'
9
+ require 'nano/helpers/accounts_proxy_helper'
10
+ require 'nano/helpers/node_proxy_helper'
11
+ require 'nano/helpers/wallet_proxy_helper'
12
+ require 'nano/proxies/account'
13
+ require 'nano/proxies/accounts'
14
+ require 'nano/proxies/node'
15
+ require 'nano/proxies/wallet'
data/nano_rpc.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'nano/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'nano_rpc'
8
+ spec.version = Nano::VERSION
9
+ spec.authors = ['Justin Craig-Kuhn (JCK)']
10
+ spec.email = ['jcraigk@gmail.com']
11
+
12
+ spec.summary = 'RPC wrapper for Nano digital nodes written in Ruby'
13
+ spec.description = 'An RPC wrapper for Nano nodes written in Ruby.' \
14
+ 'It connects to an individual node that you control. ' \
15
+ 'A client object provides arbitrary RPC access and ' \
16
+ 'proxy objects provide logically grouped helper methods.'
17
+ spec.homepage = 'https://github.com/jcraigk/ruby_nano_rpc'
18
+ spec.license = 'MIT'
19
+
20
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
21
+ f.match(%r{^(test|spec|features)/})
22
+ end
23
+ spec.bindir = 'exe'
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = %w[lib]
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.16'
28
+ spec.add_development_dependency 'pry', '~> 0.11.3'
29
+ spec.add_development_dependency 'rake', '~> 10.0'
30
+ spec.add_development_dependency 'rspec', '~> 3.0'
31
+ spec.add_development_dependency 'rubocop', '~> 0.52.1'
32
+ spec.add_development_dependency 'simplecov', '~> 0.15.1'
33
+
34
+ spec.add_runtime_dependency 'hashie', '~> 3.5', '>= 3.5.7'
35
+ spec.add_runtime_dependency 'rest-client', '~> 2.0', '>= 2.0.2'
36
+ end
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nano_rpc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Justin Craig-Kuhn (JCK)
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-01-31 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: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.11.3
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.11.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.52.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.52.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.15.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.15.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: hashie
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.5'
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: 3.5.7
107
+ type: :runtime
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - "~>"
112
+ - !ruby/object:Gem::Version
113
+ version: '3.5'
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 3.5.7
117
+ - !ruby/object:Gem::Dependency
118
+ name: rest-client
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '2.0'
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: 2.0.2
127
+ type: :runtime
128
+ prerelease: false
129
+ version_requirements: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - "~>"
132
+ - !ruby/object:Gem::Version
133
+ version: '2.0'
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: 2.0.2
137
+ description: An RPC wrapper for Nano nodes written in Ruby.It connects to an individual
138
+ node that you control. A client object provides arbitrary RPC access and proxy objects
139
+ provide logically grouped helper methods.
140
+ email:
141
+ - jcraigk@gmail.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - ".rspec"
148
+ - ".rubocop.yml"
149
+ - Gemfile
150
+ - LICENSE.txt
151
+ - README.md
152
+ - Rakefile
153
+ - bin/console
154
+ - bin/setup
155
+ - lib/nano/client.rb
156
+ - lib/nano/errors.rb
157
+ - lib/nano/helpers/account_proxy_helper.rb
158
+ - lib/nano/helpers/accounts_proxy_helper.rb
159
+ - lib/nano/helpers/node_proxy_helper.rb
160
+ - lib/nano/helpers/wallet_proxy_helper.rb
161
+ - lib/nano/proxies/account.rb
162
+ - lib/nano/proxies/accounts.rb
163
+ - lib/nano/proxies/node.rb
164
+ - lib/nano/proxies/wallet.rb
165
+ - lib/nano/proxy.rb
166
+ - lib/nano/proxy_context.rb
167
+ - lib/nano/response.rb
168
+ - lib/nano/version.rb
169
+ - lib/nano_rpc.rb
170
+ - nano_rpc.gemspec
171
+ homepage: https://github.com/jcraigk/ruby_nano_rpc
172
+ licenses:
173
+ - MIT
174
+ metadata: {}
175
+ post_install_message:
176
+ rdoc_options: []
177
+ require_paths:
178
+ - lib
179
+ required_ruby_version: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ required_rubygems_version: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ version: '0'
189
+ requirements: []
190
+ rubyforge_project:
191
+ rubygems_version: 2.7.3
192
+ signing_key:
193
+ specification_version: 4
194
+ summary: RPC wrapper for Nano digital nodes written in Ruby
195
+ test_files: []