nanorpc 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0b7a934ccc76ee58cb1d9b215e908cd4c7dc4ccf
4
+ data.tar.gz: 927b083e41c50ef9f3ad62bcda79137e4e9bde16
5
+ SHA512:
6
+ metadata.gz: 77372ba1890225c4cf9c12bbdd608f17a3921ed922a74a024b0b64167a4745888c9f8fa104a9f2c3501a816d73aeaed80c2067e0e6ac3ec62d4b77301e040fa4
7
+ data.tar.gz: ccca390ab6dede2c3f20e918fb4809358ee8d34a127083bb992fef9cd7a0a207a01e509c93956bdf9d36a217fa287b342bf1ca6f15450702c4a73a62230cffa9
@@ -0,0 +1,187 @@
1
+ require_relative "configuration.rb"
2
+
3
+ class ImmutableAccountError < StandardError
4
+ def initialize(msg="Account must be mutable to perform this action")
5
+ super
6
+ end
7
+ end
8
+
9
+ # A Nano account which has an address and a blockchain.
10
+ class NanoAccount
11
+ attr_accessor :address, :public_key, :private_key
12
+
13
+ # Create a new +NanoAccount+ instance representing an existing account.
14
+ # +public_key+ and +private_key+ are optional, but without them the account is immutable.
15
+ # Params:
16
+ # +address+:: The address of the account.
17
+ # +public_key+:: (Optional) The public key of the account.
18
+ # +private_key+:: (Optional) The private key of the account.
19
+ # +representative+:: (Optional) This block's representative. Required only if the account must be opened.
20
+ def initialize(address, public_key=nil, private_key=nil, representative=nil)
21
+ @address = address
22
+ @public_key = public_key
23
+ @private_key = private_key
24
+ @representative = representative || address
25
+ end
26
+
27
+ # Generate a new Nano account and return a mutable +NanoAccount+ instance for it.
28
+ # Params:
29
+ # +representative+:: (Optional) This account's representative. Set to the account itself if not specified.
30
+ # Returns:
31
+ # A +NanoAccount+ instance for the new account.
32
+ def self.generate(representative=nil)
33
+ result = NanoConfiguration.client.key_create
34
+ NanoAccount.new(
35
+ result["account"],
36
+ result["public"],
37
+ result["private"],
38
+ representative
39
+ )
40
+ end
41
+
42
+ # A boolean indicating whether +public_key+ and +private_key+ are specified.
43
+ def mutable?
44
+ @private_key != nil && @public_key != nil
45
+ end
46
+
47
+ # The list of pending transactions for this account.
48
+ def pending_transactions
49
+ res = NanoConfiguration.client.accounts_pending(accounts: [ @address ])["blocks"][@address]
50
+ res = [] if res == nil
51
+
52
+ res
53
+ end
54
+
55
+ # The number of blocks on this account's chain.
56
+ def block_count
57
+ begin
58
+ NanoConfiguration.client.account_block_count(account: @address)["block_count"].to_i
59
+ rescue => error
60
+ # Unopened accounts appear to throw instead
61
+ raise if error.message != "Account not found"
62
+
63
+ return 0
64
+ end
65
+ end
66
+
67
+ # Scan for any pending transactions on this chain and generate 'receive' or 'open' blocks for them.
68
+ # Requires that this account is mutable.
69
+ # Returns:
70
+ # A boolean indicating whether any transactions were pocketed or not.
71
+ def pocket
72
+ raise ImmutableAccountError unless mutable?
73
+
74
+ # Get pending transactions
75
+ pending = pending_transactions
76
+
77
+ return false if pending.length == 0
78
+
79
+ # Determine whether the first processed transaction must be an 'open' block
80
+ block_type = (block_count == 0 ? "open" : "receive")
81
+
82
+ pending.each do |pending_hash|
83
+ # NOTE: This is using implicit PoW
84
+ if block_type == "open"
85
+ # Work against the public key
86
+ work = NanoConfiguration.client.work_generate({
87
+ hash: @public_key
88
+ })["work"]
89
+
90
+ # Create a block for this 'open' transaction
91
+ block = NanoConfiguration.client.block_create({
92
+ type: block_type,
93
+ key: @private_key,
94
+ account: @address,
95
+ representative: @representative,
96
+ source: pending_hash,
97
+ work: work
98
+ })["block"]
99
+
100
+ # Since this block was an 'open', ensure that the next one is a 'receive'
101
+ block_type = "receive"
102
+ else
103
+ # Get the frontier of this chain
104
+ frontier = NanoConfiguration.client.account_info({
105
+ account: @address,
106
+ count: 1
107
+ })["frontier"]
108
+
109
+ # Work against the frontier
110
+ work = NanoConfiguration.client.work_generate({
111
+ hash: frontier
112
+ })["work"]
113
+
114
+ # Create a block for this 'receive' transaction
115
+ block = NanoConfiguration.client.block_create({
116
+ type: block_type,
117
+ key: @private_key,
118
+ account: @address,
119
+ source: pending_hash,
120
+ previous: frontier,
121
+ work: work
122
+ })["block"]
123
+ end
124
+
125
+ print "Block is: "
126
+ p block
127
+
128
+ # Publish the new block
129
+ NanoConfiguration.client.process(block: block)
130
+ end
131
+
132
+ true
133
+ end
134
+
135
+ # Send an amount of Nano to another address.
136
+ # Params:
137
+ # +amount+:: The amount of raw Nano to send.
138
+ # +recipient+:: The recipient account of the Nano, as a string or +NanoAccount+.
139
+ def send(amount, recipient)
140
+ raise ImmutableAccountError unless mutable?
141
+
142
+ if recipient.is_a?(String)
143
+ recipient = NanoAccount.new(recipient)
144
+ end
145
+
146
+ # Get the frontier of this chain
147
+ frontier = NanoConfiguration.client.account_info({
148
+ account: @address,
149
+ count: 1
150
+ })["frontier"]
151
+
152
+ # Work against the frontier
153
+ work = NanoConfiguration.client.work_generate({
154
+ hash: frontier
155
+ })["work"]
156
+
157
+ # Create a 'send' block
158
+ block = NanoConfiguration.client.block_create({
159
+ type: "send",
160
+ key: @private_key,
161
+ account: @address,
162
+ destination: recipient.address,
163
+ previous: frontier,
164
+ work: work,
165
+ amount: amount.to_i.to_s,
166
+ balance: balance
167
+ })["block"]
168
+
169
+ # Publish the block
170
+ NanoConfiguration.client.process(block: block)
171
+ end
172
+
173
+ # The total balance of the account, including unpocketed balances.
174
+ def total_balance
175
+ balance + pending_balance
176
+ end
177
+
178
+ # The balance of the account, not including unpocketed transactions.
179
+ def balance
180
+ NanoConfiguration.client.account_balance(account: @address)["balance"]
181
+ end
182
+
183
+ # The unpocketed balance of the account.
184
+ def pending_balance
185
+ NanoConfiguration.client.account_balance(account: @address)["pending"]
186
+ end
187
+ end
@@ -0,0 +1,62 @@
1
+ require "http"
2
+ require "bigdecimal"
3
+
4
+ class RpcError < StandardError
5
+ end
6
+
7
+ # A simple client for sending data to the RPC server.
8
+ # This is a low-level API; use NanoAccount instead.
9
+ class NanoRpcClient
10
+ # Create a new client.
11
+ # Params:
12
+ # +endpoint+:: The location of the RPC server.
13
+ def initialize(endpoint)
14
+ @dispatcher = HTTP
15
+ @endpoint = endpoint
16
+ end
17
+
18
+ # Make a request to the endpoint.
19
+ # Params:
20
+ # +data+:: A +Hash+ to send to the RPC server.
21
+ # Returns:
22
+ # The object returned by the server as a +Hash+.
23
+ def req(data)
24
+ res = @dispatcher.post(@endpoint, :json => data).parse
25
+
26
+ raise RpcError, res["error"] if res["error"] != nil
27
+
28
+ res
29
+ end
30
+
31
+ # Missing methods will make requests to the RPC server using the method name as the action.
32
+ # The rest of the keyword arguments are passed as JSON data.
33
+ # Params:
34
+ # +action+:: The +action+ field of the RPC request.
35
+ # +kwargs+:: The keyword arguments passed to the method.
36
+ def method_missing(action, **kwargs)
37
+ kwargs["action"] = action.to_s
38
+
39
+ # Stringify all keys
40
+ req(kwargs.map{|k, v| [k.to_s, v]}.to_h)
41
+ end
42
+
43
+ # Convert an amount of MRai to a raw Nano quantity.
44
+ # Params:
45
+ # +mrai+:: The quantity of MRai.
46
+ # Returns:
47
+ # The quantity of raw Nano, stringified.
48
+ def self.mrai_to_raw(mrai)
49
+ BigDecimal(mrai) * BigDecimal("1000000000000000000000000000000")
50
+ end
51
+ end
52
+
53
+ # A variant of +NanoRpcClient+ which connects public to Nanode servers using an API key.
54
+ class NanodeClient < NanoRpcClient
55
+ # Create a new Nanode client.
56
+ # Params:
57
+ # +key+:: A valid Nanode API key.
58
+ def initialize(key)
59
+ @dispatcher = HTTP.auth(key)
60
+ @endpoint = "https://api.nanode.co/"
61
+ end
62
+ end
@@ -0,0 +1,16 @@
1
+ # Configure the NanoRpc library.
2
+ class NanoConfiguration
3
+ @@client = nil
4
+
5
+ # Set the client used to make requests.
6
+ # +new_client+:: The new client, which should be an instance of +NanoRpcClient+.
7
+ def self.client=(new_client)
8
+ @@client = new_client
9
+ end
10
+
11
+ # Get the client used to make requests.
12
+ def self.client
13
+ throw "Set NanoConfiguration.client before making RPC calls" if @@client == nil
14
+ @@client
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ require_relative "account.rb"
2
+ require_relative "clients.rb"
3
+ require_relative "configuration.rb"
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nanorpc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Christiansen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-02-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: http
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 3.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 3.0.0
33
+ description:
34
+ email:
35
+ executables: []
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - lib/account.rb
40
+ - lib/clients.rb
41
+ - lib/configuration.rb
42
+ - lib/nanorpc.rb
43
+ homepage:
44
+ licenses:
45
+ - MIT
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 2.5.2.1
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Communicate with the Nano/RaiBlocks network
67
+ test_files: []