cns 0.7.4 → 0.7.6

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 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