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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +26 -0
  6. data/CHANGELOG.md +24 -0
  7. data/CODE_OF_CONDUCT.md +84 -0
  8. data/Gemfile +12 -0
  9. data/Gemfile.lock +114 -0
  10. data/Guardfile +79 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +747 -0
  13. data/Rakefile +12 -0
  14. data/bin/console +15 -0
  15. data/bin/setup +8 -0
  16. data/docker-compose.yml +12 -0
  17. data/flow.json +21 -0
  18. data/flow_client.gemspec +44 -0
  19. data/lib/cadence/contracts/NonFungibleToken.cdc +144 -0
  20. data/lib/cadence/templates/add-account-key.cdc +16 -0
  21. data/lib/cadence/templates/add-contract.cdc +5 -0
  22. data/lib/cadence/templates/create-account.cdc +21 -0
  23. data/lib/cadence/templates/remove-contract.cdc +5 -0
  24. data/lib/cadence/templates/update-contract.cdc +5 -0
  25. data/lib/flow/access/access_pb.rb +168 -0
  26. data/lib/flow/access/access_services_pb.rb +96 -0
  27. data/lib/flow/entities/account_pb.rb +30 -0
  28. data/lib/flow/entities/block_header_pb.rb +20 -0
  29. data/lib/flow/entities/block_pb.rb +25 -0
  30. data/lib/flow/entities/block_seal_pb.rb +19 -0
  31. data/lib/flow/entities/collection_pb.rb +23 -0
  32. data/lib/flow/entities/event_pb.rb +20 -0
  33. data/lib/flow/entities/transaction_pb.rb +47 -0
  34. data/lib/flow/execution/execution_pb.rb +65 -0
  35. data/lib/flow/execution/execution_services_pb.rb +43 -0
  36. data/lib/flow/legacy/access/access_pb.rb +157 -0
  37. data/lib/flow/legacy/access/access_services_pb.rb +89 -0
  38. data/lib/flow/legacy/entities/account_pb.rb +28 -0
  39. data/lib/flow/legacy/entities/block_header_pb.rb +20 -0
  40. data/lib/flow/legacy/entities/block_pb.rb +25 -0
  41. data/lib/flow/legacy/entities/block_seal_pb.rb +19 -0
  42. data/lib/flow/legacy/entities/collection_pb.rb +22 -0
  43. data/lib/flow/legacy/entities/event_pb.rb +20 -0
  44. data/lib/flow/legacy/entities/transaction_pb.rb +45 -0
  45. data/lib/flow/legacy/execution/execution_pb.rb +65 -0
  46. data/lib/flow/legacy/execution/execution_services_pb.rb +42 -0
  47. data/lib/flow_client/account.rb +31 -0
  48. data/lib/flow_client/block.rb +81 -0
  49. data/lib/flow_client/cadence_type.rb +185 -0
  50. data/lib/flow_client/client.rb +387 -0
  51. data/lib/flow_client/collection.rb +35 -0
  52. data/lib/flow_client/crypto.rb +62 -0
  53. data/lib/flow_client/event.rb +52 -0
  54. data/lib/flow_client/proposal_key.rb +23 -0
  55. data/lib/flow_client/signature.rb +23 -0
  56. data/lib/flow_client/signer.rb +22 -0
  57. data/lib/flow_client/transaction.rb +190 -0
  58. data/lib/flow_client/utils.rb +67 -0
  59. data/lib/flow_client/version.rb +5 -0
  60. data/lib/flow_client.rb +22 -0
  61. data/logo.svg +121 -0
  62. data/logo@2x.png +0 -0
  63. data/template.md +748 -0
  64. 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