nano_rpc 0.1.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
+ 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: []