cns 0.7.4 → 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7880ff81313f41571c941a08e2f77d3f3725e19a68b5603aaa0fa00760e98f46
4
- data.tar.gz: da5bba73d734f8263506662a7a966628e32f95ca4bc5cba9bedff0294dd30e12
3
+ metadata.gz: c057fdcc8c76136db6203c763bb68b551c712a3023e705915f88c54b1d08ca7a
4
+ data.tar.gz: 1cc5eeb11f55d0cb3cbee6b80eb3d2978c8ff201ae58a18c58a9d930b774811d
5
5
  SHA512:
6
- metadata.gz: fb30d1db97b27bef9caa97c6d6ecc8a319707a5ddba20b382b21b8607b31b46ec1f0d6f269d7e3defd733f0881e6a6a3a67d41b26b767b3e830226611df54997
7
- data.tar.gz: 8eec81e398a8358b47051dcabbc70dd3fbf1c7faf3bbeee05d5cc26d3cec36bfb1968af98caf80a1a43d823b3523184e3d1791933c4704724d292b0422910195
6
+ metadata.gz: b1d061301afeafd4643509e0ce966cff4f5fce04970dea65a78a7ba901d5cf1c71f38030b4a8ffe9f13437a3bf511e58b673881c8f956f6f91c988131fb943c0
7
+ data.tar.gz: 457cf0ab8be639742b00b26d0ffab798fc6d313b264f2ba52ae6f821ef766f9fc0bf524b8ec1d0e29d9d22dcece13cb5e603a1a288fb72a2e5241d327025b900
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cns (0.7.4)
4
+ cns (0.7.6)
5
5
  curb
6
6
  faraday
7
7
  google-cloud-bigquery
@@ -62,7 +62,7 @@ GEM
62
62
  signet (>= 0.16, < 2.a)
63
63
  httpclient (2.8.3)
64
64
  jaro_winkler (1.6.0)
65
- json (2.9.1)
65
+ json (2.10.1)
66
66
  jwt (2.10.1)
67
67
  base64
68
68
  kramdown (2.5.1)
@@ -120,6 +120,7 @@ GEM
120
120
  parser (~> 2.2)
121
121
  slop (~> 3.4, >= 3.4.7)
122
122
  ruby-progressbar (1.13.0)
123
+ rufo (0.18.0)
123
124
  signet (0.19.0)
124
125
  addressable (~> 2.8)
125
126
  faraday (>= 0.17.5, < 3.a)
@@ -159,6 +160,7 @@ DEPENDENCIES
159
160
  rubocop
160
161
  rubocop-rake
161
162
  ruby-lint
163
+ rufo
162
164
  solargraph
163
165
  yard
164
166
 
data/cns.gemspec CHANGED
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
32
32
  spec.add_development_dependency('rubocop')
33
33
  spec.add_development_dependency('rubocop-rake')
34
34
  spec.add_development_dependency('ruby-lint')
35
+ spec.add_development_dependency('rufo')
35
36
  spec.add_development_dependency('solargraph')
36
37
  spec.add_development_dependency('yard')
37
38
 
data/lib/cns/apibc.rb CHANGED
@@ -1,386 +1,137 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require('faraday')
4
- require('json')
3
+ require("faraday")
4
+ require("json")
5
5
 
6
6
  # @author Hernani Rodrigues Vaz
7
7
  module Cns
8
8
  # classe para acesso dados blockchains
9
9
  class Apibc
10
- # @example account_es
11
- # {
12
- # status: '1',
13
- # message: 'OK',
14
- # result: [
15
- # { account: '0x...', balance: '4000000000000000000' },
16
- # { account: '0x...', balance: '87000000000000000000' }
17
- # ]
18
- # }
19
- # @param [Array<String>] ads lista enderecos ETH (max 20)
20
- # @return [Array<Hash>] lista enderecos e seus saldos
21
- def account_es(ads)
22
- JSON.parse(
23
- conn_es.get('/api', action: 'balancemulti', tag: 'latest', address: ads.join(',')).body,
24
- symbolize_names: true
25
- )[:result]
26
- rescue StandardError
27
- []
10
+ # Get account balances for multiple ETH addresses
11
+ # @param addresses [Array<String>] List of ETH addresses (max 20)
12
+ # @return [Array<Hash>] List of addresses with balances
13
+ def account_es(addresses)
14
+ response = etherscan_req("balancemulti", addresses.join(","), 1, tag: "latest")
15
+ response[:status] == "1" ? response.dig(:result) : []
28
16
  end
29
17
 
30
- # @example account_gm
31
- # {
32
- # account_name: '...',
33
- # head_block_num: 141_391_122,
34
- # head_block_time: '2020-09-11T16:05:51.000',
35
- # privileged: false,
36
- # last_code_update: '1970-01-01T00:00:00.000',
37
- # created: '2018-06-09T13:14:37.000',
38
- #
39
- # core_liquid_balance: '1232.0228 EOS',
40
- # total_resources: { owner: '...', net_weight: '1000.1142 EOS', cpu_weight: '1000.1144 EOS', ram_bytes: 8148 },
41
- #
42
- # ram_quota: 9548,
43
- # net_weight: 10_001_142,
44
- # cpu_weight: 10_001_144,
45
- # net_limit: { used: 0, available: 1_066_648_346, max: 1_066_648_346 },
46
- # cpu_limit: { used: 338, available: 88_498, max: 88_836 },
47
- # ram_usage: 3574,
48
- # permissions: [
49
- # {
50
- # perm_name: 'active',
51
- # parent: 'owner',
52
- # required_auth: {
53
- # threshold: 1, keys: [{ key: 'EOS...', weight: 1 }], accounts: [], waits: []
54
- # }
55
- # },
56
- # {
57
- # perm_name: 'owner',
58
- # parent: '',
59
- # required_auth: {
60
- # threshold: 1, keys: [{ key: 'EOS...', weight: 1 }], accounts: [], waits: []
61
- # }
62
- # }
63
- # ],
64
- # self_delegated_bandwidth: { from: '...', to: '...', net_weight: '1000.1142 EOS', cpu_weight: '1000.1144 EOS' },
65
- # refund_request: nil,
66
- # voter_info: {
67
- # owner: '...',
68
- # proxy: '...',
69
- # producers: [],
70
- # staked: 20_002_286,
71
- # last_vote_weight: '17172913021904.12109375000000000',
72
- # proxied_vote_weight: '0.00000000000000000',
73
- # is_proxy: 0,
74
- # flags1: 0,
75
- # reserved2: 0,
76
- # reserved3: '0.0000 EOS'
77
- # },
78
- # rex_info: nil
79
- # }
80
- # @param [String] add endereco EOS
81
- # @return [Hash] endereco e seus saldo/recursos
82
- def account_gm(add)
83
- JSON.parse(conn_gm.post('/v1/chain/get_account', { account_name: add }.to_json).body, symbolize_names: true)
84
- rescue StandardError
85
- { core_liquid_balance: 0, total_resources: { net_weight: 0, cpu_weight: 0 } }
18
+ # Get EOS account information
19
+ # @param address [String] EOS account name
20
+ # @return [Hash] Account details with resources
21
+ def account_gm(address)
22
+ response = greymass_req("/v1/chain/get_account", { account_name: address })
23
+ response || { core_liquid_balance: 0, total_resources: { net_weight: 0, cpu_weight: 0 } }
86
24
  end
87
25
 
88
- # @example norml_es
89
- # {
90
- # status: '1',
91
- # message: 'OK',
92
- # result: [
93
- # {
94
- # blockNumber: '4984535',
95
- # timeStamp: '1517094794',
96
- # hash: '0x...',
97
- # nonce: '10',
98
- # blockHash: '0x...',
99
- # transactionIndex: '17',
100
- # from: '0x...',
101
- # to: '0x...',
102
- # value: '52627271000000000000',
103
- # gas: '21000',
104
- # gasPrice: '19000000000',
105
- # isError: '0',
106
- # txreceipt_status: '1',
107
- # input: '0x',
108
- # contractAddress: '',
109
- # gasUsed: '21000',
110
- # cumulativeGasUsed: '566293',
111
- # confirmations: '5848660'
112
- # },
113
- # {}
114
- # ]
115
- # }
116
- # @param [String] add endereco ETH
117
- # @param [Integer] pag pagina transacoes
118
- # @param [Array<Hash>] aes acumulador transacoes
26
+ # Get normal transactions for ETH address
27
+ # @param [String] address endereco ETH
119
28
  # @return [Array<Hash>] lista transacoes normais etherscan
120
- def norml_es(add, pag = 0, aes = [])
121
- res = JSON.parse(
122
- conn_es.get('/api', action: 'txlist', offset: 10_000, address: add, page: pag += 1).body,
123
- symbolize_names: true
124
- )[:result]
125
- aes += res
126
- res.count < 10_000 ? aes : norml_es(add, pag, aes)
127
- rescue StandardError
128
- aes
29
+ def norml_es(address)
30
+ pag_etherscan_req("txlist", address)
129
31
  end
130
32
 
131
- # @example inter_es
132
- # {
133
- # status: '1',
134
- # message: 'OK',
135
- # result: [
136
- # {
137
- # blockNumber: '15592786',
138
- # timeStamp: '1663896779',
139
- # hash: '0x...',
140
- # from: '0x...',
141
- # to: '0x...',
142
- # value: '22000000000000000',
143
- # contractAddress: '',
144
- # input: '',
145
- # type: 'call',
146
- # gas: '2300',
147
- # gasUsed: '0',
148
- # traceId: '0_1_1',
149
- # isError: '0',
150
- # errCode: ''
151
- # }
152
- # ]
153
- # }
33
+ # Get internal transactions for ETH address
154
34
  # @param (see norml_es)
155
35
  # @return [Array<Hash>] lista transacoes internas etherscan
156
- def inter_es(add, pag = 0, aes = [])
157
- res = JSON.parse(
158
- conn_es.get('/api', action: 'txlistinternal', offset: 10_000, address: add, page: pag += 1).body,
159
- symbolize_names: true
160
- )[:result]
161
- aes += res
162
- res.count < 10_000 ? aes : inter_es(add, pag, aes)
163
- rescue StandardError
164
- aes
36
+ def inter_es(address)
37
+ pag_etherscan_req("txlistinternal", address)
165
38
  end
166
39
 
167
- # @example block_es
168
- # {
169
- # status: '1',
170
- # message: 'OK',
171
- # result: [
172
- # { blockNumber: '15737070', timeStamp: '1665638459', blockReward: '8922867945448231' },
173
- # { blockNumber: '15592786', timeStamp: '1663896779', blockReward: '44997990286623752' },
174
- # { blockNumber: '15570222', timeStamp: '1663622819', blockReward: '261211525682683684' },
175
- # { blockNumber: '15568554', timeStamp: '1663602539', blockReward: '41993833217879559' }
176
- # ]
177
- # }
40
+ # Get mined blocks for ETH address
178
41
  # @param (see norml_es)
179
42
  # @return [Array<Hash>] lista blocos etherscan
180
- def block_es(add, pag = 0, aes = [])
181
- res = JSON.parse(
182
- conn_es.get(
183
- '/api',
184
- action: 'getminedblocks',
185
- blocktype: 'blocks',
186
- offset: 10_000,
187
- address: add,
188
- page: pag += 1
189
- ).body,
190
- symbolize_names: true
191
- )[:result]
192
- aes += res
193
- res.count < 10_000 ? aes : block_es(add, pag, aes)
194
- rescue StandardError
195
- aes
43
+ def block_es(address)
44
+ pag_etherscan_req("getminedblocks", address, blocktype: "blocks")
196
45
  end
197
46
 
198
- # @example withw_es
199
- # {
200
- # "status":"1",
201
- # "message":"OK",
202
- # "result":[
203
- # {
204
- # "withdrawalIndex":"14",
205
- # "validatorIndex":"119023",
206
- # "address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f",
207
- # "amount":"3244098967",
208
- # "blockNumber":"17034877",
209
- # "timestamp":"1681338599"
210
- # }
211
- # ]
212
- # }
47
+ # Get withdrawals for ETH address
213
48
  # @param (see norml_es)
214
49
  # @return [Array<Hash>] lista blocos etherscan
215
- def withw_es(add, pag = 0, aes = [])
216
- res = JSON.parse(
217
- conn_es.get(
218
- '/api',
219
- action: 'txsBeaconWithdrawal',
220
- address: add,
221
- offset: 10_000,
222
- page: pag += 1
223
- ).body,
224
- symbolize_names: true
225
- )[:result]
226
- aes += res
227
- res.count < 10_000 ? aes : withw_es(add, pag, aes)
228
- rescue StandardError
229
- aes
50
+ def withw_es(address)
51
+ pag_etherscan_req("txsBeaconWithdrawal", address)
230
52
  end
231
53
 
232
- # @example token_es
233
- # {
234
- # status: '1',
235
- # message: 'OK',
236
- # result: [
237
- # {
238
- # blockNumber: '3967652',
239
- # timeStamp: '1499081515',
240
- # hash: '0x registo duplicado com todos os dados iguais',
241
- # nonce: '3',
242
- # blockHash: '0x00a49e999036dc13dc6c4244bb1d51d3146fe7f00bfb500a7624d82e299c7328',
243
- # from: '0xd0a6e6c54dbc68db5db3a091b171a77407ff7ccf',
244
- # contractAddress: '0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0',
245
- # to: '0x...',
246
- # value: '0',
247
- # tokenName: 'EOS',
248
- # tokenSymbol: 'EOS',
249
- # tokenDecimal: '18',
250
- # transactionIndex: '83',
251
- # gas: '173399',
252
- # gasPrice: '21000000000',
253
- # gasUsed: '173398',
254
- # input: 'deprecated',
255
- # cumulativeGasUsed: '7484878',
256
- # confirmations: '3442641'
257
- # },
258
- # {}
259
- # ]
260
- # }
54
+ # Get token transfers for ETH address
261
55
  # @param (see norml_es)
262
56
  # @return [Array<Hash>] lista token transfer events etherscan
263
- def token_es(add, pag = 0, aes = [])
264
- # registos duplicados aparecem em token events (ver exemplo acima)
265
- # -quando ha erros na blockchain (acho)
266
- # -quando ha token events identicos no mesmo block (acho)
267
- res = JSON.parse(
268
- conn_es.get('/api', action: 'tokentx', offset: 10_000, address: add, page: pag += 1).body,
269
- symbolize_names: true
270
- )[:result]
271
- aes += res
272
- res.count < 10_000 ? aes : token_es(add, pag, aes)
273
- rescue StandardError
274
- aes
57
+ def token_es(address)
58
+ pag_etherscan_req("tokentx", address)
275
59
  end
276
60
 
277
- # @example ledger_gm
278
- # {
279
- # actions: [
280
- # {
281
- # account_action_seq: 964,
282
- # action_trace: {
283
- # account_ram_deltas: [],
284
- # act: {
285
- # account: 'voicebestapp',
286
- # authorization: [
287
- # { actor: 'thetruevoice', permission: 'active' },
288
- # { actor: 'voicebestapp', permission: 'active' }
289
- # ],
290
- # data: { from: 'voicebestapp', memo: '...', quantity: '1.0001 MESSAGE', to: '...' },
291
- # hex_data: '...',
292
- # name: 'transfer'
293
- # },
294
- # action_ordinal: 10,
295
- # block_num: 141_345_345,
296
- # block_time: '2020-09-11T09:44:04.500',
297
- # closest_unnotified_ancestor_action_ordinal: 5,
298
- # context_free: false,
299
- # creator_action_ordinal: 5,
300
- # elapsed: 6,
301
- # producer_block_id: '...',
302
- # receipt: {
303
- # abi_sequence: 1,
304
- # act_digest: '...',
305
- # auth_sequence: [['thetruevoice', 6_778_215], ['voicebestapp', 435_346]],
306
- # code_sequence: 1,
307
- # global_sequence: 233_283_589_258,
308
- # receiver: '...',
309
- # recv_sequence: 927
310
- # },
311
- # receiver: '...',
312
- # trx_id: '...'
313
- # },
314
- # block_num: 141_345_345,
315
- # block_time: '2020-09-11T09:44:04.500',
316
- # global_action_seq: 233_283_589_258,
317
- # irreversible: true
318
- # },
319
- # {}
320
- # ],
321
- # head_block_num: 141_721_698,
322
- # last_irreversible_block: 141_721_371
323
- # }
324
- # @param add (see account_gm)
325
- # @param [Array<Hash>] agm acumulador transacoes
61
+ # Get complete transaction history for EOS account
62
+ # @param (see account_gm)
326
63
  # @return [Array<Hash>] lista completa transacoes greymass
327
- def ledger_gm(add, agm = [])
328
- res = JSON.parse(
329
- conn_gm.post('/v1/history/get_actions', { account_name: add, pos: agm.count, offset: 100 }.to_json).body,
330
- symbolize_names: true
331
- )[:actions]
332
- agm += res
333
- res.count < 100 ? agm : ledger_gm(add, agm)
334
- rescue StandardError
335
- agm
64
+ def ledger_gm(address)
65
+ actions = []
66
+ pos = 0
67
+ loop do
68
+ response = greymass_req("/v1/history/get_actions", { account_name: address, pos: pos, offset: 100 })
69
+ batch = response[:actions] || []
70
+ actions += batch
71
+ break if batch.size < 100
72
+
73
+ pos += 100
74
+ end
75
+ actions
336
76
  end
337
77
 
338
78
  private
339
79
 
340
- # @return [<Symbol>] adapter for the connection - default :net_http
341
- def adapter
342
- @adapter ||= Faraday.default_adapter
80
+ # Reusable Faraday connection
81
+ def connection(base_url)
82
+ Faraday.new(base_url) do |conn|
83
+ conn.headers = { content_type: "application/json", accept: "application/json", user_agent: "blockchain-api-client" }
84
+ conn.adapter Faraday.default_adapter
85
+ end
86
+ end
87
+
88
+ # Generic Etherscan API request handler
89
+ def etherscan_req(action, address, page = 1, params = {})
90
+ params = {
91
+ module: "account",
92
+ action: action,
93
+ address: address,
94
+ page: page,
95
+ apikey: ENV.fetch("ETHERSCAN_API_KEY"),
96
+ }.merge(params)
97
+ parse_json(connection("https://api.etherscan.io").get("/api", params).body)
98
+ rescue Faraday::Error, JSON::ParserError
99
+ { status: "0", result: [] }
343
100
  end
344
101
 
345
- # @return [<Faraday::Connection>] connection object for etherscan
346
- def conn_es
347
- @conn_es ||=
348
- Faraday.new(
349
- url: 'https://api.etherscan.io',
350
- params: { module: 'account', apikey: ENV.fetch('ETHERSCAN_API_KEY', nil) },
351
- headers: { content_type: 'application/json', accept: 'application/json', user_agent: 'etherscan;ruby' }
352
- ) do |con|
353
- con.request(:url_encoded)
354
- con.adapter(adapter)
355
- end
102
+ # Generic method for paginated Etherscan requests
103
+ # @param action [String] API action name
104
+ # @param address [String] Blockchain address
105
+ # @param params [Hash] Additional request parameters
106
+ # @return [Array<Hash>] Combined results from all pages
107
+ def pag_etherscan_req(action, address, params = {})
108
+ results = []
109
+ page = 1
110
+ loop do
111
+ response = etherscan_req(action, address, page, params)
112
+ break unless response[:status] == "1"
113
+
114
+ batch = response[:result] || []
115
+ results += batch
116
+ break if batch.size < 10000
117
+
118
+ page += 1
119
+ end
120
+ results
356
121
  end
357
122
 
358
- # @return [<Faraday::Connection>] connection object for greymass
359
- def conn_gm
360
- @conn_gm ||=
361
- Faraday.new(url: 'https://eos.greymass.com', headers: { content_type: 'application/json' }) do |con|
362
- con.request(:url_encoded)
363
- con.adapter(adapter)
364
- end
123
+ # Generic Greymass API request handler
124
+ def greymass_req(endpoint, payload)
125
+ parse_json((connection("https://eos.greymass.com").post(endpoint) { |req| req.body = payload.to_json }).body)
126
+ rescue Faraday::Error, JSON::ParserError
127
+ nil
365
128
  end
366
129
 
367
- # @param [String] uri ETH2 API
368
- # @return [Array<Hash>] lista dados beaconchain
369
- # def data_bc(uri)
370
- # res = JSON.parse(conn_bc.get(uri).body, symbolize_names: true)[:data] || []
371
- # # calls are rate limited to 10 requests/minute/IP
372
- # sleep(3)
373
- # res.is_a?(Array) ? res : [res]
374
- # rescue StandardError
375
- # []
376
- # end
377
- # @return [<Faraday::Connection>] connection object for beaconchain
378
- # def conn_bc
379
- # @conn_bc ||=
380
- # Faraday.new(url: 'https://beaconcha.in', headers: { accept: 'application/json' }) do |con|
381
- # con.request(:url_encoded)
382
- # con.adapter(adapter)
383
- # end
384
- # end
130
+ # Safe JSON parsing with error handling
131
+ def parse_json(body)
132
+ JSON.parse(body, symbolize_names: true)
133
+ rescue JSON::ParserError
134
+ {}
135
+ end
385
136
  end
386
137
  end
data/lib/cns/bigquery.rb CHANGED
@@ -169,12 +169,12 @@ module Cns
169
169
  def apies
170
170
  @apies ||= Etherscan.new(
171
171
  {
172
- wb: sql("select * from #{BD}.wetb order by 2"),
173
- ni: sql("select * from #{BD}.netai"),
174
- nk: sql("select * from #{BD}.netak"),
175
- np: sql("select * from #{BD}.netap"),
176
- nt: sql("select * from #{BD}.netat"),
177
- nw: sql("select * from #{BD}.netaw")
172
+ wb: sql("select * from #{BD}.wetb order by ax"),
173
+ ni: sql("select * from #{BD}.netbi"),
174
+ nk: sql("select * from #{BD}.netbk"),
175
+ np: sql("select * from #{BD}.netbp"),
176
+ nt: sql("select * from #{BD}.netbt"),
177
+ nw: sql("select * from #{BD}.netbw")
178
178
  },
179
179
  ops
180
180
  )
@@ -184,12 +184,12 @@ module Cns
184
184
  def apiesc
185
185
  @apies ||= Etherscan.new(
186
186
  {
187
- wb: sql("select * from #{BD}.wetc order by 2"),
188
- ni: sql("select * from #{BD}.netbi"),
189
- nk: sql("select * from #{BD}.netbk"),
190
- np: sql("select * from #{BD}.netbp"),
191
- nt: sql("select * from #{BD}.netbt"),
192
- nw: sql("select * from #{BD}.netbw")
187
+ wb: sql("select * from #{BD}.wetc order by ax"),
188
+ ni: sql("select * from #{BD}.netci"),
189
+ nk: sql("select * from #{BD}.netck"),
190
+ np: sql("select * from #{BD}.netcp"),
191
+ nt: sql("select * from #{BD}.netct"),
192
+ nw: sql("select * from #{BD}.netcw")
193
193
  },
194
194
  ops
195
195
  )
@@ -199,7 +199,7 @@ module Cns
199
199
  def apigm
200
200
  @apigm ||= Greymass.new(
201
201
  {
202
- wb: sql("select * from #{BD}.weos order by 2"),
202
+ wb: sql("select * from #{BD}.weos order by ax"),
203
203
  nt: sql("select * from #{BD}.neosx")
204
204
  },
205
205
  ops
data/lib/cns/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cns
4
- VERSION = '0.7.4'
4
+ VERSION = '0.7.6'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.4
4
+ version: 0.7.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hernâni Rodrigues Vaz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-10 00:00:00.000000000 Z
11
+ date: 2025-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rufo
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: solargraph
99
113
  requirement: !ruby/object:Gem::Requirement