nanorpc 0.0.1

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