erc20 0.2.3 → 0.2.4

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: c000b5455dfb26a625500563fa1906f58d441b55b0e03f5e7e60c7e9f308dfd7
4
- data.tar.gz: 63b09e6727a881dc625c5e68cbef7a301f3f5f9e27d3d5ef07e1d8e4a0a4d4a3
3
+ metadata.gz: e5b9458add234faedc7e3827c363cf16b083b2c409b13577a7b30ac601d0ddef
4
+ data.tar.gz: 4593c178c97685ec74c1c2bc0855daddf19082c6b9eead27254d79d644c2a0e2
5
5
  SHA512:
6
- metadata.gz: c112d03a57b44edb7e48135340d2b88edcdf6abcb0cc0d4ad0091b94d1e82fbc1fade682b5f4a49a85064e8a2e7efdd7ceba31fc5d772e28608ff6b18efe70d8
7
- data.tar.gz: 8adfe06ede8c5ba56388c406e59e95fabed750536e8e505bcfacafcb92eb09b059b594f7c8b826be770d36d2b6cd673a7cbb8d7a8dd257e06ecf7e9635ce617a
6
+ metadata.gz: 7c2459cfdc15924c674852a078116ad91e0c78fa38e522f4bf1075fb409a8756c7f73f9931206c6f3e16c98119ca278c04a6cd0f2ed7470cb765ab1a35284a87
7
+ data.tar.gz: 8ad83464049ed10ec9d261b8ffbd2b1a889d56fc376c2c79fa159fc95f4bba48786fbbab54ab855461f81e10bc551c494a61cd1907f3b5b1636084e9bd04b34e
data/Gemfile.lock CHANGED
@@ -2,6 +2,7 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  erc20 (0.0.0)
5
+ elapsed (~> 0.2)
5
6
  eth (~> 0.5)
6
7
  faye-websocket (~> 0.11)
7
8
  json (~> 2.10)
@@ -56,7 +57,7 @@ GEM
56
57
  backtrace (~> 0.3)
57
58
  os (~> 1.1)
58
59
  qbash (~> 0.3)
59
- elapsed (0.2.0)
60
+ elapsed (0.2.1)
60
61
  loog (~> 0.6)
61
62
  tago (~> 0.1)
62
63
  erb (6.0.1)
data/bin/erc20 CHANGED
@@ -36,6 +36,11 @@ Options are:"
36
36
  'Ethereum chain ID',
37
37
  default: 1
38
38
  )
39
+ o.bool(
40
+ '--ssl',
41
+ 'Use SSL for HTTP connections',
42
+ default: true
43
+ )
39
44
  o.string(
40
45
  '--host',
41
46
  'Host name of the provider',
@@ -80,12 +85,18 @@ Options are:"
80
85
  end
81
86
  raise 'Try --help' if opts.arguments.empty?
82
87
  log = opts[:verbose] ? Loog::VERBOSE : Loog::REGULAR
83
- wallet = ERC20::Wallet.new(
84
- contract: opts[:contract],
85
- host: opts[:host], port: opts[:port],
86
- http_path: opts[:http_path], ws_path: opts[:ws_path],
87
- log:
88
- )
88
+ wallet =
89
+ if opts[:dry]
90
+ ERC20::FakeWallet.new
91
+ else
92
+ ERC20::Wallet.new(
93
+ contract: opts[:contract],
94
+ host: opts[:host], port: opts[:port].to_i,
95
+ http_path: opts[:http_path], ws_path: opts[:ws_path],
96
+ ssl: opts[:ssl],
97
+ log:
98
+ )
99
+ end
89
100
  case opts.arguments[0]
90
101
  when 'key'
91
102
  puts Eth::Key.new.private_hex
@@ -119,14 +130,20 @@ Options are:"
119
130
  raise 'Amount argument is required (for example, "19.9usdt" or "$19.9")' if amount.nil?
120
131
  if /^[0-9]+$/.match?(amount)
121
132
  amount = amount.to_i
133
+ log.debug("The token amount equals to #{amount} ERC20 tokens (as provided)")
122
134
  elsif /^[0-9]+(\.[0-9]+)?usdt$/.match?(amount)
123
135
  amount = (amount.gsub(/usdt$/, '').to_f * 1_000_000).to_i
136
+ log.debug("The USDT amount equals to #{amount} ERC20 tokens")
124
137
  elsif /^\$[0-9]+(\.[0-9]+)?$/.match?(amount)
125
138
  amount = (amount.gsub(/^\$/, '').to_f * 1_000_000).to_i
139
+ log.debug("The dollar amount equals to #{amount} ERC20 tokens")
126
140
  else
127
141
  raise "Can't understand amount: #{amount.inspect}"
128
142
  end
129
143
  log.debug("Sending #{amount} ERC20 tokens")
144
+ if wallet.eth_balance(priv.address.to_s).zero?
145
+ log.debug("ETH balance of #{priv.address} is zero, the txn most definitely will be rejected by the server!")
146
+ end
130
147
  puts wallet.pay(priv.private_hex, address, amount)
131
148
  when 'eth_pay'
132
149
  pkey = opts.arguments[1]
data/erc20.gemspec CHANGED
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
25
25
  s.rdoc_options = ['--charset=UTF-8']
26
26
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
27
27
  s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
28
+ s.add_dependency 'elapsed', '~>0.2'
28
29
  s.add_dependency 'eth', '~>0.5'
29
30
  s.add_dependency 'faye-websocket', '~>0.11'
30
31
  s.add_dependency 'json', '~>2.10'
@@ -0,0 +1,21 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ Feature: Command Line Processing, in Dry Mode
5
+ As a simple ETH/ERC20 user I want to test it in dry mode
6
+
7
+ Scenario: Gas price price can be retrieved
8
+ When I run bin/erc20 with "price --attempts=4 --dry"
9
+ Then Exit code is zero
10
+
11
+ Scenario: ERC20 balance can be checked
12
+ When I run bin/erc20 with "balance 0x7232148927F8a580053792f44D4d59d40Fd00ABD --verbose --dry"
13
+ Then Exit code is zero
14
+
15
+ Scenario: ETH balance can be checked
16
+ When I run bin/erc20 with "eth_balance 0x7232148927F8a580053792f44D4d59d40Fd00ABD --verbose --dry"
17
+ Then Exit code is zero
18
+
19
+ Scenario: ERC20 payment can be sent
20
+ When I run bin/erc20 with "pay --dry --verbose 46feba063e9b59a8ae0dba68abd39a3cb8f52089e776576d6eb1bb5bfec123d1 0x7232148927F8a580053792f44D4d59d40Fd00ABD $10"
21
+ Then Exit code is zero
data/lib/erc20/erc20.rb CHANGED
@@ -25,5 +25,5 @@
25
25
  # License:: MIT
26
26
  module ERC20
27
27
  # Current version of the gem (changed by the +.rultor.yml+ on every release)
28
- VERSION = '0.2.3'
28
+ VERSION = '0.2.4'
29
29
  end
data/lib/erc20/wallet.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
+ require 'elapsed'
6
7
  require 'eth'
7
8
  require 'eventmachine'
8
9
  require 'faye/websocket'
@@ -119,7 +120,10 @@ class ERC20::Wallet
119
120
  raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
120
121
  func = '70a08231' # balanceOf
121
122
  data = "0x#{func}000000000000000000000000#{address[2..].downcase}"
122
- r = jsonrpc.eth_call({ to: @contract, data: data }, 'latest')
123
+ r =
124
+ with_jsonrpc do |jr|
125
+ jr.eth_call({ to: @contract, data: data }, 'latest')
126
+ end
123
127
  b = r[2..].to_i(16)
124
128
  log_it(:debug, "The balance of #{address} is #{b} ERC20 tokens")
125
129
  b
@@ -136,7 +140,10 @@ class ERC20::Wallet
136
140
  raise 'Address can\'t be nil' unless address
137
141
  raise 'Address must be a String' unless address.is_a?(String)
138
142
  raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
139
- r = jsonrpc.eth_getBalance(address, 'latest')
143
+ r =
144
+ with_jsonrpc do |jr|
145
+ jr.eth_getBalance(address, 'latest')
146
+ end
140
147
  b = r[2..].to_i(16)
141
148
  log_it(:debug, "The balance of #{address} is #{b} ETHs")
142
149
  b
@@ -150,7 +157,10 @@ class ERC20::Wallet
150
157
  raise 'Transaction hash can\'t be nil' unless txn
151
158
  raise 'Transaction hash must be a String' unless txn.is_a?(String)
152
159
  raise 'Invalid format of the transaction hash' unless /^0x[0-9a-fA-F]{64}$/.match?(txn)
153
- receipt = jsonrpc.eth_getTransactionReceipt(txn)
160
+ receipt =
161
+ with_jsonrpc do |jr|
162
+ jr.eth_getTransactionReceipt(txn)
163
+ end
154
164
  raise "Transaction not found: #{txn}" if receipt.nil?
155
165
  logs = receipt['logs'] || []
156
166
  transfer_event = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
@@ -180,7 +190,10 @@ class ERC20::Wallet
180
190
  raise 'Amount can\'t be nil' unless amount
181
191
  raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
182
192
  raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
183
- gas = jsonrpc.eth_estimateGas({ from:, to: @contract, data: to_pay_data(to, amount) }, 'latest').to_i(16)
193
+ gas =
194
+ with_jsonrpc do |jr|
195
+ jr.eth_estimateGas({ from:, to: @contract, data: to_pay_data(to, amount) }, 'latest').to_i(16)
196
+ end
184
197
  log_it(:debug, "It would take #{gas} gas units to send #{amount} tokens from #{from} to #{to}")
185
198
  gas
186
199
  end
@@ -196,7 +209,10 @@ class ERC20::Wallet
196
209
  #
197
210
  # @return [Integer] Price of gas unit, in gwei (0.000000001 ETH)
198
211
  def gas_price
199
- block = jsonrpc.eth_getBlockByNumber('latest', false)
212
+ block =
213
+ with_jsonrpc do |jr|
214
+ jr.eth_getBlockByNumber('latest', false)
215
+ end
200
216
  raise "Can't get gas price, try again later" if block.nil?
201
217
  gwei = block['baseFeePerGas'].to_i(16)
202
218
  log_it(:debug, "The cost of one gas unit is #{gwei} gwei")
@@ -241,9 +257,9 @@ class ERC20::Wallet
241
257
  from = key.address.to_s
242
258
  tnx =
243
259
  @mutex.synchronize do
244
- nonce = jsonrpc.eth_getTransactionCount(from, 'pending').to_i(16)
245
- tx = Eth::Tx.new(
246
- {
260
+ with_jsonrpc do |jr|
261
+ nonce = jr.eth_getTransactionCount(from, 'pending').to_i(16)
262
+ h = {
247
263
  nonce:,
248
264
  gas_price: price,
249
265
  gas_limit: limit || gas_estimate(from, address, amount),
@@ -252,10 +268,12 @@ class ERC20::Wallet
252
268
  data: to_pay_data(address, amount),
253
269
  chain_id: @chain
254
270
  }
255
- )
256
- tx.sign(key)
257
- hex = "0x#{tx.hex}"
258
- jsonrpc.eth_sendRawTransaction(hex)
271
+ tx = Eth::Tx.new(h)
272
+ tx.sign(key)
273
+ hex = "0x#{tx.hex}"
274
+ log_it(:debug, "Sending ERC20 transaction #{hex}")
275
+ jr.eth_sendRawTransaction(hex)
276
+ end
259
277
  end
260
278
  log_it(:debug, "Sent #{amount} ERC20 tokens from #{from} to #{address}: #{tnx}")
261
279
  tnx.downcase
@@ -286,9 +304,9 @@ class ERC20::Wallet
286
304
  from = key.address.to_s
287
305
  tnx =
288
306
  @mutex.synchronize do
289
- nonce = jsonrpc.eth_getTransactionCount(from, 'pending').to_i(16)
290
- tx = Eth::Tx.new(
291
- {
307
+ with_jsonrpc do |jr|
308
+ nonce = jr.eth_getTransactionCount(from, 'pending').to_i(16)
309
+ h = {
292
310
  chain_id: @chain,
293
311
  nonce:,
294
312
  gas_price: price,
@@ -296,10 +314,12 @@ class ERC20::Wallet
296
314
  to: address,
297
315
  value: amount
298
316
  }
299
- )
300
- tx.sign(key)
301
- hex = "0x#{tx.hex}"
302
- jsonrpc.eth_sendRawTransaction(hex)
317
+ tx = Eth::Tx.new(h)
318
+ tx.sign(key)
319
+ hex = "0x#{tx.hex}"
320
+ log_it(:debug, "Sending ETH transaction #{hex}")
321
+ jr.eth_sendRawTransaction(hex)
322
+ end
303
323
  end
304
324
  log_it(:debug, "Sent #{amount} ETHs from #{from} to #{address}: #{tnx}")
305
325
  tnx.downcase
@@ -474,7 +494,7 @@ class ERC20::Wallet
474
494
  URI.parse("#{http ? 'http' : 'ws'}#{'s' if @ssl}://#{@host}:#{@port}#{http ? @http_path : @ws_path}")
475
495
  end
476
496
 
477
- def jsonrpc
497
+ def with_jsonrpc
478
498
  JSONRPC.logger = Loog::NULL
479
499
  opts = {}
480
500
  if @proxy
@@ -489,7 +509,9 @@ class ERC20::Wallet
489
509
  }
490
510
  end
491
511
  end
492
- JSONRPC::Client.new(url.to_s, opts)
512
+ elapsed(@log, good: "Talked to #{url.host}:#{url.port}") do
513
+ yield JSONRPC::Client.new(url.to_s, opts)
514
+ end
493
515
  end
494
516
 
495
517
  def to_pay_data(address, amount)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: erc20
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: elapsed
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.2'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: eth
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -115,6 +129,7 @@ files:
115
129
  - bin/erc20
116
130
  - erc20.gemspec
117
131
  - features/cli.feature
132
+ - features/dry.feature
118
133
  - features/gem_package.feature
119
134
  - features/step_definitions/steps.rb
120
135
  - features/support/env.rb