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.
- checksums.yaml +7 -0
- data/lib/account.rb +187 -0
- data/lib/clients.rb +62 -0
- data/lib/configuration.rb +16 -0
- data/lib/nanorpc.rb +3 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -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
|
data/lib/account.rb
ADDED
@@ -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
|
data/lib/clients.rb
ADDED
@@ -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
|
data/lib/nanorpc.rb
ADDED
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: []
|