flow_client 0.2.3-arm64-darwin-21
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/.github/workflows/ruby.yml +37 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +26 -0
- data/CHANGELOG.md +24 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +114 -0
- data/Guardfile +79 -0
- data/LICENSE.txt +21 -0
- data/README.md +747 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +12 -0
- data/flow.json +21 -0
- data/flow_client.gemspec +44 -0
- data/lib/cadence/contracts/NonFungibleToken.cdc +144 -0
- data/lib/cadence/templates/add-account-key.cdc +16 -0
- data/lib/cadence/templates/add-contract.cdc +5 -0
- data/lib/cadence/templates/create-account.cdc +21 -0
- data/lib/cadence/templates/remove-contract.cdc +5 -0
- data/lib/cadence/templates/update-contract.cdc +5 -0
- data/lib/flow/access/access_pb.rb +168 -0
- data/lib/flow/access/access_services_pb.rb +96 -0
- data/lib/flow/entities/account_pb.rb +30 -0
- data/lib/flow/entities/block_header_pb.rb +20 -0
- data/lib/flow/entities/block_pb.rb +25 -0
- data/lib/flow/entities/block_seal_pb.rb +19 -0
- data/lib/flow/entities/collection_pb.rb +23 -0
- data/lib/flow/entities/event_pb.rb +20 -0
- data/lib/flow/entities/transaction_pb.rb +47 -0
- data/lib/flow/execution/execution_pb.rb +65 -0
- data/lib/flow/execution/execution_services_pb.rb +43 -0
- data/lib/flow/legacy/access/access_pb.rb +157 -0
- data/lib/flow/legacy/access/access_services_pb.rb +89 -0
- data/lib/flow/legacy/entities/account_pb.rb +28 -0
- data/lib/flow/legacy/entities/block_header_pb.rb +20 -0
- data/lib/flow/legacy/entities/block_pb.rb +25 -0
- data/lib/flow/legacy/entities/block_seal_pb.rb +19 -0
- data/lib/flow/legacy/entities/collection_pb.rb +22 -0
- data/lib/flow/legacy/entities/event_pb.rb +20 -0
- data/lib/flow/legacy/entities/transaction_pb.rb +45 -0
- data/lib/flow/legacy/execution/execution_pb.rb +65 -0
- data/lib/flow/legacy/execution/execution_services_pb.rb +42 -0
- data/lib/flow_client/account.rb +31 -0
- data/lib/flow_client/block.rb +81 -0
- data/lib/flow_client/cadence_type.rb +185 -0
- data/lib/flow_client/client.rb +387 -0
- data/lib/flow_client/collection.rb +35 -0
- data/lib/flow_client/crypto.rb +62 -0
- data/lib/flow_client/event.rb +52 -0
- data/lib/flow_client/proposal_key.rb +23 -0
- data/lib/flow_client/signature.rb +23 -0
- data/lib/flow_client/signer.rb +22 -0
- data/lib/flow_client/transaction.rb +190 -0
- data/lib/flow_client/utils.rb +67 -0
- data/lib/flow_client/version.rb +5 -0
- data/lib/flow_client.rb +22 -0
- data/logo.svg +121 -0
- data/logo@2x.png +0 -0
- data/template.md +748 -0
- metadata +192 -0
@@ -0,0 +1,387 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "flow/access/access_services_pb"
|
4
|
+
require "flow/execution/execution_services_pb"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
# Collection of classes to interact with the Flow blockchain
|
8
|
+
module FlowClient
|
9
|
+
class CadenceRuntimeError < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
class ClientError < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
# Flow client
|
16
|
+
class Client
|
17
|
+
attr_accessor :address_aliases
|
18
|
+
|
19
|
+
def initialize(node_address)
|
20
|
+
@stub = Access::AccessAPI::Stub.new(node_address, :this_channel_is_insecure)
|
21
|
+
@address_aliases = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def ping
|
25
|
+
req = Access::PingRequest.new
|
26
|
+
@stub.ping(req)
|
27
|
+
end
|
28
|
+
|
29
|
+
# :section: Accounts
|
30
|
+
|
31
|
+
# Returns an account for the address specified at the latest
|
32
|
+
# block.
|
33
|
+
#
|
34
|
+
# @param [String] the address string value
|
35
|
+
#
|
36
|
+
# @return [FlowClient::Account] the account
|
37
|
+
def get_account(address)
|
38
|
+
req = Access::GetAccountAtLatestBlockRequest.new(address: to_bytes(address))
|
39
|
+
|
40
|
+
begin
|
41
|
+
res = @stub.get_account_at_latest_block(req)
|
42
|
+
rescue GRPC::BadStatus => e
|
43
|
+
raise ClientError, e.details
|
44
|
+
else
|
45
|
+
account = FlowClient::Account.new(
|
46
|
+
address: res.account.address.unpack1("H*"),
|
47
|
+
balance: res.account.balance/100000000.0
|
48
|
+
)
|
49
|
+
|
50
|
+
res.account.keys.each do |key|
|
51
|
+
account.keys << FlowClient::AccountKey.new(
|
52
|
+
public_key: key.public_key.unpack1("H*"),
|
53
|
+
index: key.index,
|
54
|
+
sequence_number: key.sequence_number,
|
55
|
+
revoked: key.revoked,
|
56
|
+
weight: key.weight
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
account.contracts = res.account.contracts
|
61
|
+
account
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Creates a new account
|
66
|
+
#
|
67
|
+
# @return [FlowClient::Account] the newly created account
|
68
|
+
def create_account(new_account_public_keys, contracts, payer_account, signer)
|
69
|
+
script = File.read(File.join("lib", "cadence", "templates", "create-account.cdc"))
|
70
|
+
|
71
|
+
arguments = [
|
72
|
+
CadenceType.Array(
|
73
|
+
new_account_public_keys.to_a.map { |key| CadenceType.String(key) }
|
74
|
+
),
|
75
|
+
CadenceType.Dictionary(
|
76
|
+
contracts.to_a.map { |name, code| CadenceType.DictionaryValue(
|
77
|
+
CadenceType.String(name), CadenceType.String(code.unpack1("H*"))
|
78
|
+
) }
|
79
|
+
),
|
80
|
+
]
|
81
|
+
|
82
|
+
transaction = FlowClient::Transaction.new
|
83
|
+
transaction.script = script
|
84
|
+
transaction.reference_block_id = get_latest_block.id
|
85
|
+
transaction.proposer_address = payer_account.address
|
86
|
+
transaction.proposer_key_index = 0
|
87
|
+
transaction.arguments = arguments
|
88
|
+
transaction.proposer_key_sequence_number = get_account(payer_account.address).keys.first.sequence_number
|
89
|
+
transaction.payer_address = payer_account.address
|
90
|
+
transaction.authorizer_addresses = [payer_account.address]
|
91
|
+
transaction.add_envelope_signature(payer_account.address, 0, signer)
|
92
|
+
res = send_transaction(transaction)
|
93
|
+
|
94
|
+
new_account = nil
|
95
|
+
wait_for_transaction(res.id) do |response|
|
96
|
+
raise CadenceRuntimeError, response.error_message if response.status_code != 0
|
97
|
+
|
98
|
+
event_payload = response.events.select { |e| e.type == "flow.AccountCreated" }.first.payload
|
99
|
+
payload_json = JSON.parse(event_payload)
|
100
|
+
new_account_address = payload_json["value"]["fields"][0]["value"]["value"]
|
101
|
+
new_account = get_account(new_account_address)
|
102
|
+
end
|
103
|
+
|
104
|
+
new_account
|
105
|
+
end
|
106
|
+
|
107
|
+
# Adds a public key to an account
|
108
|
+
def add_account_key(public_key_hex, payer_account, signer, weight)
|
109
|
+
script = File.read(File.join("lib", "cadence", "templates", "add-account-key.cdc"))
|
110
|
+
|
111
|
+
arguments = [
|
112
|
+
CadenceType.String(public_key_hex),
|
113
|
+
CadenceType.UFix64(weight)
|
114
|
+
]
|
115
|
+
|
116
|
+
transaction = FlowClient::Transaction.new
|
117
|
+
transaction.script = script
|
118
|
+
transaction.reference_block_id = get_latest_block.id
|
119
|
+
transaction.proposer_address = payer_account.address
|
120
|
+
transaction.proposer_key_index = 0
|
121
|
+
transaction.arguments = arguments
|
122
|
+
transaction.proposer_key_sequence_number = get_account(payer_account.address).keys.first.sequence_number
|
123
|
+
transaction.payer_address = payer_account.address
|
124
|
+
transaction.authorizer_addresses = [payer_account.address]
|
125
|
+
transaction.add_envelope_signature(payer_account.address, 0, signer)
|
126
|
+
res = send_transaction(transaction)
|
127
|
+
|
128
|
+
wait_for_transaction(res.id) do |response|
|
129
|
+
raise CadenceRuntimeError, response.error_message if response.status_code != 0
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Adds a contract to an account
|
134
|
+
def add_contract(name, code, payer_account, signer)
|
135
|
+
script = File.read(File.join("lib", "cadence", "templates", "add-contract.cdc"))
|
136
|
+
code_hex = code.unpack1("H*")
|
137
|
+
|
138
|
+
arguments = [
|
139
|
+
CadenceType.String(name),
|
140
|
+
CadenceType.String(code_hex)
|
141
|
+
]
|
142
|
+
|
143
|
+
transaction = FlowClient::Transaction.new
|
144
|
+
transaction.script = script
|
145
|
+
transaction.reference_block_id = get_latest_block.id
|
146
|
+
transaction.proposer_address = payer_account.address
|
147
|
+
transaction.proposer_key_index = 0
|
148
|
+
transaction.arguments = arguments
|
149
|
+
transaction.proposer_key_sequence_number = get_account(payer_account.address).keys.first.sequence_number
|
150
|
+
transaction.payer_address = payer_account.address
|
151
|
+
transaction.authorizer_addresses = [payer_account.address]
|
152
|
+
transaction.add_envelope_signature(payer_account.address, 0, signer)
|
153
|
+
res = send_transaction(transaction)
|
154
|
+
|
155
|
+
wait_for_transaction(res.id) do |response|
|
156
|
+
raise CadenceRuntimeError, response.error_message if response.status_code != 0
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Removes a contract from an account
|
161
|
+
def remove_contract(name, payer_account, signer)
|
162
|
+
script = File.read(File.join("lib", "cadence", "templates", "remove-contract.cdc"))
|
163
|
+
|
164
|
+
arguments = [
|
165
|
+
CadenceType.String(name),
|
166
|
+
]
|
167
|
+
|
168
|
+
transaction = FlowClient::Transaction.new
|
169
|
+
transaction.script = script
|
170
|
+
transaction.reference_block_id = get_latest_block.id
|
171
|
+
transaction.proposer_address = payer_account.address
|
172
|
+
transaction.proposer_key_index = 0
|
173
|
+
transaction.arguments = arguments
|
174
|
+
transaction.proposer_key_sequence_number = get_account(payer_account.address).keys.first.sequence_number
|
175
|
+
transaction.payer_address = payer_account.address
|
176
|
+
transaction.authorizer_addresses = [payer_account.address]
|
177
|
+
transaction.add_envelope_signature(payer_account.address, 0, signer)
|
178
|
+
res = send_transaction(transaction)
|
179
|
+
|
180
|
+
wait_for_transaction(res.id) do |response|
|
181
|
+
raise CadenceRuntimeError, response.error_message if response.status_code != 0
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Updates a contract on an account
|
186
|
+
def update_contract(name, code, payer_account, signer)
|
187
|
+
script = File.read(File.join("lib", "cadence", "templates", "update-contract.cdc"))
|
188
|
+
code_hex = code.unpack1("H*")
|
189
|
+
|
190
|
+
arguments = [
|
191
|
+
CadenceType.String(name),
|
192
|
+
CadenceType.String(code_hex)
|
193
|
+
]
|
194
|
+
|
195
|
+
transaction = FlowClient::Transaction.new
|
196
|
+
transaction.script = script
|
197
|
+
transaction.reference_block_id = get_latest_block.id
|
198
|
+
transaction.proposer_address = payer_account.address
|
199
|
+
transaction.proposer_key_index = 0
|
200
|
+
transaction.arguments = arguments
|
201
|
+
transaction.proposer_key_sequence_number = get_account(payer_account.address).keys.first.sequence_number
|
202
|
+
transaction.payer_address = payer_account.address
|
203
|
+
transaction.authorizer_addresses = [payer_account.address]
|
204
|
+
transaction.add_envelope_signature(payer_account.address, 0, signer)
|
205
|
+
res = send_transaction(transaction)
|
206
|
+
|
207
|
+
wait_for_transaction(res.id) do |response|
|
208
|
+
raise CadenceRuntimeError, response.error_message if response.status_code != 0
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# :section: Scripts
|
213
|
+
|
214
|
+
# Executes a script on the blockchain
|
215
|
+
def execute_script(script, args = [])
|
216
|
+
processed_args = []
|
217
|
+
args.to_a.each do |arg|
|
218
|
+
processed_arg = arg.class == OpenStruct ? Utils.openstruct_to_json(arg) : arg
|
219
|
+
processed_args << processed_arg
|
220
|
+
end
|
221
|
+
|
222
|
+
req = Access::ExecuteScriptAtLatestBlockRequest.new(
|
223
|
+
script: FlowClient::Utils.substitute_address_aliases(script, @address_aliases),
|
224
|
+
arguments: processed_args
|
225
|
+
)
|
226
|
+
|
227
|
+
res = @stub.execute_script_at_latest_block(req)
|
228
|
+
parse_json(res.value)
|
229
|
+
end
|
230
|
+
|
231
|
+
# :section: Blocks
|
232
|
+
|
233
|
+
# Returns the latest block
|
234
|
+
#
|
235
|
+
# @return [FlowClient::Block] the block
|
236
|
+
def get_latest_block(is_sealed: true)
|
237
|
+
req = Access::GetLatestBlockRequest.new(
|
238
|
+
is_sealed: is_sealed
|
239
|
+
)
|
240
|
+
res = @stub.get_latest_block(req)
|
241
|
+
Block.parse_grpc_block_response(res)
|
242
|
+
end
|
243
|
+
|
244
|
+
# Returns the block with id
|
245
|
+
#
|
246
|
+
# @return [FlowClient::Block] the block
|
247
|
+
def get_block_by_id(id)
|
248
|
+
req = Access::GetBlockByIDRequest.new(
|
249
|
+
id: to_bytes(id)
|
250
|
+
)
|
251
|
+
res = @stub.get_block_by_id(req)
|
252
|
+
Block.parse_grpc_block_response(res)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Returns the latest with height
|
256
|
+
#
|
257
|
+
# @param [Integer] block height
|
258
|
+
#
|
259
|
+
# @return [FlowClient::Block] the block
|
260
|
+
def get_block_by_height(height)
|
261
|
+
req = Access::GetBlockByHeightRequest.new(
|
262
|
+
height: height
|
263
|
+
)
|
264
|
+
res = @stub.get_block_by_height(req)
|
265
|
+
Block.parse_grpc_block_response(res)
|
266
|
+
end
|
267
|
+
|
268
|
+
# :section: Collections
|
269
|
+
|
270
|
+
# Returns the collection with id
|
271
|
+
#
|
272
|
+
# @param [String] collection id
|
273
|
+
#
|
274
|
+
# @return [FlowClient::Collection] the collection
|
275
|
+
def get_collection_by_id(id)
|
276
|
+
req = Access::GetCollectionByIDRequest.new(
|
277
|
+
id: to_bytes(id)
|
278
|
+
)
|
279
|
+
res = @stub.get_collection_by_id(req)
|
280
|
+
Collection.parse_grpc_type(res)
|
281
|
+
end
|
282
|
+
|
283
|
+
# :section: Events
|
284
|
+
|
285
|
+
# Returns events of the given type between the start and end block heights
|
286
|
+
#
|
287
|
+
# @param [String] event name
|
288
|
+
# @param [Integer] start block height
|
289
|
+
# @param [Integer] end block height
|
290
|
+
#
|
291
|
+
# @return [FlowClient::EventsResult] the events response
|
292
|
+
def get_events(type, start_height, end_height)
|
293
|
+
req = Access::GetEventsForHeightRangeRequest.new(
|
294
|
+
type: type,
|
295
|
+
start_height: start_height,
|
296
|
+
end_height: end_height
|
297
|
+
)
|
298
|
+
begin
|
299
|
+
res = @stub.get_events_for_height_range(req)
|
300
|
+
rescue GRPC::BadStatus => e
|
301
|
+
raise ClientError, e.details
|
302
|
+
else
|
303
|
+
res.results.map { |event| EventsResult.parse_grpc_type(event) }
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# :section: Transactions
|
308
|
+
|
309
|
+
# Sends a transaction to the blockchain
|
310
|
+
#
|
311
|
+
# @return [FlowClient::TransactionResponse] the transaction response
|
312
|
+
def send_transaction(transaction)
|
313
|
+
transaction.address_aliases = @address_aliases
|
314
|
+
req = Access::SendTransactionRequest.new(
|
315
|
+
transaction: transaction.to_protobuf_message
|
316
|
+
)
|
317
|
+
|
318
|
+
begin
|
319
|
+
res = @stub.send_transaction(req)
|
320
|
+
rescue GRPC::BadStatus => e
|
321
|
+
raise ClientError, e.details
|
322
|
+
else
|
323
|
+
TransactionResponse.parse_grpc_type(res)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# Returns the transaction with transaction_id
|
328
|
+
#
|
329
|
+
# @return [FlowClient::Transaction] the transaction
|
330
|
+
def get_transaction(transaction_id)
|
331
|
+
req = Access::GetTransactionRequest.new(
|
332
|
+
id: to_bytes(transaction_id)
|
333
|
+
)
|
334
|
+
|
335
|
+
begin
|
336
|
+
res = @stub.get_transaction(req)
|
337
|
+
rescue GRPC::BadStatus => e
|
338
|
+
raise ClientError, e.details
|
339
|
+
else
|
340
|
+
Transaction.parse_grpc_type(res.transaction)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# Returns a transaction result
|
345
|
+
#
|
346
|
+
# @return [FlowClient::TransactionResult] the transaction result
|
347
|
+
def get_transaction_result(transaction_id)
|
348
|
+
req = Access::GetTransactionRequest.new(
|
349
|
+
id: to_bytes(transaction_id)
|
350
|
+
)
|
351
|
+
|
352
|
+
begin
|
353
|
+
res = @stub.get_transaction_result(req)
|
354
|
+
rescue GRPC::BadStatus => e
|
355
|
+
raise ClientError, e.details
|
356
|
+
else
|
357
|
+
TransactionResult.parse_grpc_type(res)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
# Polls the blockchain for the transaction result until it is sealed
|
362
|
+
# or expired
|
363
|
+
def wait_for_transaction(transaction_id)
|
364
|
+
response = get_transaction_result(transaction_id)
|
365
|
+
while ![:SEALED, :EXPIRED].include? response.status
|
366
|
+
sleep(0.5)
|
367
|
+
response = get_transaction_result(transaction_id)
|
368
|
+
end
|
369
|
+
|
370
|
+
yield(response)
|
371
|
+
end
|
372
|
+
|
373
|
+
private
|
374
|
+
|
375
|
+
def parse_json(event_payload)
|
376
|
+
JSON.parse(event_payload, object_class: OpenStruct)
|
377
|
+
end
|
378
|
+
|
379
|
+
def to_bytes(string)
|
380
|
+
[string].pack("H*")
|
381
|
+
end
|
382
|
+
|
383
|
+
def to_string(bytes)
|
384
|
+
bytes.unpack1("H*")
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowClient
|
4
|
+
class Collection
|
5
|
+
attr_accessor :id, :transaction_ids
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@id = nil
|
9
|
+
@transaction_ids = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.parse_grpc_type(grpc_type)
|
13
|
+
collection = Collection.new
|
14
|
+
collection.id = grpc_type.collection.id.unpack1("H*")
|
15
|
+
collection.transaction_ids = grpc_type.collection.transaction_ids.to_a.map { |tid| tid.unpack1("H*") }
|
16
|
+
collection
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class CollectionGuarantee
|
21
|
+
attr_accessor :collection_id, :signatures
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@collection_id = nil
|
25
|
+
@signatures = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.parse_grpc_type(grpc_type)
|
29
|
+
collection_guarantee = CollectionGuarantee.new
|
30
|
+
collection_guarantee.collection_id = grpc_type.collection_id.unpack1("H*")
|
31
|
+
collection_guarantee.signatures = grpc_type.signatures.to_a.map { |s| s.unpack1("H*") }
|
32
|
+
collection_guarantee
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
|
5
|
+
module FlowClient
|
6
|
+
# Crypto helpers
|
7
|
+
class Crypto
|
8
|
+
module Curves
|
9
|
+
P256 = "prime256v1"
|
10
|
+
SECP256K1 = "secp256k1"
|
11
|
+
end
|
12
|
+
|
13
|
+
module HashAlgos
|
14
|
+
SHA2_256 = "SHA2-256"
|
15
|
+
SHA3_256 = "SHA3-256"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Sign data using the provided key
|
19
|
+
def self.sign(data, private_key_hex, hash_algo = HashAlgos::SHA3_256)
|
20
|
+
ssl_key = FlowClient::Crypto.key_from_hex_keys(private_key_hex)
|
21
|
+
# TODO: Fix this so that both hashing algos will work
|
22
|
+
asn = ssl_key.dsa_sign_asn1(OpenSSL::Digest.digest(hash_algo, data))
|
23
|
+
r, s = OpenSSL::ASN1.decode(asn).value
|
24
|
+
combined_bytes = Utils.left_pad_bytes([r.value.to_s(16)].pack("H*").unpack("C*"), 32) +
|
25
|
+
Utils.left_pad_bytes([s.value.to_s(16)].pack("H*").unpack("C*"), 32)
|
26
|
+
combined_bytes.pack("C*")
|
27
|
+
end
|
28
|
+
|
29
|
+
# Constructs an OpenSSL::PKey::EC key from an octet string
|
30
|
+
# keypair.
|
31
|
+
#
|
32
|
+
# secp256k1
|
33
|
+
# prime256v1
|
34
|
+
def self.key_from_hex_keys(private_hex, curve = Curves::P256)
|
35
|
+
group = OpenSSL::PKey::EC::Group.new(curve)
|
36
|
+
new_key = OpenSSL::PKey::EC.new(group)
|
37
|
+
new_key.private_key = OpenSSL::BN.new(private_hex, 16)
|
38
|
+
new_key.public_key = group.generator.mul(new_key.private_key)
|
39
|
+
new_key
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns an octet string keypair.
|
43
|
+
#
|
44
|
+
# Supported ECC curves are:
|
45
|
+
# Crypto::Curves::P256
|
46
|
+
# Crypto::Curves::SECP256K1
|
47
|
+
#
|
48
|
+
# The 04 prefix indicating that the public key is uncompressed is stripped.
|
49
|
+
# @see https://datatracker.ietf.org/doc/html/rfc5480
|
50
|
+
#
|
51
|
+
# Usage example:
|
52
|
+
# private_key, public_key = FlowClient::Crypto.generate_key_pair(FlowClient::Crypto::Curves::P256)
|
53
|
+
def self.generate_key_pair(curve = Curves::P256)
|
54
|
+
key = OpenSSL::PKey::EC.new(curve).generate_key
|
55
|
+
public_key = key.public_key.to_bn.to_s(16).downcase
|
56
|
+
[
|
57
|
+
key.private_key.to_s(16).downcase,
|
58
|
+
public_key[2..public_key.length]
|
59
|
+
]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowClient
|
4
|
+
class EventsResult
|
5
|
+
attr_accessor :block_id,
|
6
|
+
:block_height,
|
7
|
+
:events,
|
8
|
+
:block_timestamp
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@block_id = nil
|
12
|
+
@block_height = nil
|
13
|
+
@events = nil
|
14
|
+
@block_timestamp = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.parse_grpc_type(type)
|
18
|
+
event = EventsResult.new
|
19
|
+
event.block_id = type.block_id.unpack1("H*")
|
20
|
+
event.block_height = type.block_height
|
21
|
+
event.block_timestamp = FlowClient::Utils.parse_protobuf_timestamp(type.block_timestamp)
|
22
|
+
event.events = type.events.map { |event| FlowClient::Event.parse_grpc_type(event) }
|
23
|
+
event
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Event
|
28
|
+
attr_accessor :type,
|
29
|
+
:transaction_id,
|
30
|
+
:transaction_index,
|
31
|
+
:event_index,
|
32
|
+
:payload
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@type = nil
|
36
|
+
@transaction_id = nil
|
37
|
+
@transaction_index = nil
|
38
|
+
@event_index = nil
|
39
|
+
@payload = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.parse_grpc_type(type)
|
43
|
+
event = Event.new
|
44
|
+
event.type = type.type
|
45
|
+
event.transaction_id = type.transaction_id.unpack1("H*")
|
46
|
+
event.transaction_index = type.transaction_index
|
47
|
+
event.event_index = type.event_index
|
48
|
+
event.payload = type.payload
|
49
|
+
event
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowClient
|
4
|
+
class ProposalKey
|
5
|
+
attr_accessor :address,
|
6
|
+
:sequence_number,
|
7
|
+
:key_id
|
8
|
+
|
9
|
+
def initialize(address: nil, key_id: nil, sequence_number: nil)
|
10
|
+
@address = address
|
11
|
+
@sequence_number = sequence_number
|
12
|
+
@key_id = key_id
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.parse_grpc_type(type)
|
16
|
+
signature = ProposalKey.new
|
17
|
+
signature.address = type.address.unpack1("H*")
|
18
|
+
signature.sequence_number = type.sequence_number
|
19
|
+
signature.key_id = type.key_id
|
20
|
+
signature
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowClient
|
4
|
+
class Signature
|
5
|
+
attr_accessor :address,
|
6
|
+
:signature,
|
7
|
+
:key_id
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@address = nil
|
11
|
+
@signature = nil
|
12
|
+
@key_id = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.parse_grpc_type(pb_signature)
|
16
|
+
signature = Signature.new
|
17
|
+
signature.address = pb_signature.address.unpack1("H*")
|
18
|
+
signature.signature = pb_signature.signature.unpack1("H*")
|
19
|
+
signature.key_id = pb_signature.key_id
|
20
|
+
signature
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowClient
|
4
|
+
# An abstract super class for the transaction signers. Subclasses must
|
5
|
+
# implement the sign method to sign transactions.
|
6
|
+
class Signer
|
7
|
+
def sign(data); end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Implements a local singer using an in-memory key.
|
11
|
+
class LocalSigner < Signer
|
12
|
+
def initialize(private_key)
|
13
|
+
super()
|
14
|
+
@private_key = private_key
|
15
|
+
end
|
16
|
+
|
17
|
+
def sign(data)
|
18
|
+
super(data)
|
19
|
+
FlowClient::Crypto.sign(data, @private_key)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|