erc20 0.0.15 → 0.0.17

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: 9aaaf16c1cb37a5f676efbec190e3b707c34fa816e781d3119c43b5b2b44500b
4
- data.tar.gz: a77d26b7f77b56f9a46f860b4a5e5104ed7239da40f16d14b056c96b8d05ca88
3
+ metadata.gz: 53d1ed4b12b1b7e295242f5345c3ebd4f22e8fe83af31bd2bcb4f27ec8a0b4ca
4
+ data.tar.gz: e377b3185614081e92f72a7fda5c9a8019de72a3497b9d28259e13f17a5f1ab0
5
5
  SHA512:
6
- metadata.gz: edef3ec85bb02e73d8815907bf6f256b1a261b8051ada8a1280efde631d8faaa70e5533c4577e63843615a163e4115e383ce4d7d891592277fb71377d1eafffd
7
- data.tar.gz: 484da28c8385d9a5b4ce18ca0cffd26e221144d641d1e9f777980a6394edcac0843a9f5e58e2243e7f858aea561ff8a39466934eefd01cfebced3071a029f24b
6
+ metadata.gz: 892709a9568e7837a708feefd8d7880d309fae9f921d1a8266e1b7b995acb1575a6305af92a1dd5dab7b9e7a7f3da9cc5b0b2539a9984eac91d291f59c5d86d1
7
+ data.tar.gz: d945d2ec483e4cc14ff76683294941cfaf9603963baf8c3acbf64189ee59aead1ec70e6b2d292c889d790c8f8f6fcd59c5327bb2a5223a138821fd5592971178
data/.rubocop.yml CHANGED
@@ -63,3 +63,4 @@ Security/MarshalLoad:
63
63
  Enabled: false
64
64
  Layout/MultilineAssignmentLayout:
65
65
  Enabled: true
66
+ require: []
data/Gemfile CHANGED
@@ -35,10 +35,10 @@ gem 'rake', '13.2.1', require: false
35
35
  gem 'random-port', '>0', require: false
36
36
  gem 'rspec-rails', '7.1.1', require: false
37
37
  gem 'rubocop', '1.72.1', require: false
38
- gem 'rubocop-minitest', '0.37.1', require: false
39
- gem 'rubocop-performance', '1.24.0', require: false
38
+ gem 'rubocop-minitest', '>0', require: false
39
+ gem 'rubocop-performance', '>0', require: false
40
40
  gem 'rubocop-rake', '>0', require: false
41
- gem 'rubocop-rspec', '3.4.0', require: false
41
+ gem 'rubocop-rspec', '>0', require: false
42
42
  gem 'simplecov', '0.22.0', require: false
43
43
  gem 'simplecov-cobertura', '2.1.0', require: false
44
44
  gem 'threads', '0.4.1', require: false
data/Gemfile.lock CHANGED
@@ -184,7 +184,7 @@ GEM
184
184
  regexp_parser (2.10.0)
185
185
  reline (0.6.0)
186
186
  io-console (~> 0.5)
187
- rexml (3.4.0)
187
+ rexml (3.4.1)
188
188
  rspec-core (3.13.3)
189
189
  rspec-support (~> 3.13.0)
190
190
  rspec-expectations (3.13.3)
@@ -288,10 +288,10 @@ DEPENDENCIES
288
288
  random-port (> 0)
289
289
  rspec-rails (= 7.1.1)
290
290
  rubocop (= 1.72.1)
291
- rubocop-minitest (= 0.37.1)
292
- rubocop-performance (= 1.24.0)
291
+ rubocop-minitest (> 0)
292
+ rubocop-performance (> 0)
293
293
  rubocop-rake (> 0)
294
- rubocop-rspec (= 3.4.0)
294
+ rubocop-rspec (> 0)
295
295
  simplecov (= 0.22.0)
296
296
  simplecov-cobertura (= 2.1.0)
297
297
  threads (= 0.4.1)
data/README.md CHANGED
@@ -37,13 +37,23 @@ hex = w.pay(private_key, to_address, amount)
37
37
  # Stay waiting, and trigger the block when new ERC20 payments show up:
38
38
  addresses = ['0x...', '0x...'] # only wait for payments to these addresses
39
39
  w.accept(addresses) do |event|
40
- puts event[:txt] # hash of transaction
40
+ puts event[:txn] # hash of transaction
41
41
  puts event[:amount] # how much, in tokens (1000000 = $1 USDT)
42
42
  puts event[:from] # who sent the payment
43
43
  puts event[:to] # who was the receiver
44
44
  end
45
45
  ```
46
46
 
47
+ It's also possible to check ETH balance and send ETH transaction:
48
+
49
+ ```ruby
50
+ # Check how many ETHs are there on the given address:
51
+ eth = w.eth_balance(address)
52
+
53
+ # Send a few ETHs to someone and get transaction hash:
54
+ hex = w.eth_pay(private_key, to_address, amount)
55
+ ```
56
+
47
57
  To generate a new private key, use [eth](https://rubygems.org/gems/eth):
48
58
 
49
59
  ```ruby
@@ -51,7 +61,7 @@ require 'eth'
51
61
  key = Eth::Key.new.private_hex
52
62
  ```
53
63
 
54
- To get address from private one:
64
+ To convert a private key to a public address:
55
65
 
56
66
  ```ruby
57
67
  public_hex = Eth::Key.new(priv: key).address
data/lib/erc20/erc20.rb CHANGED
@@ -42,5 +42,5 @@
42
42
  # License:: MIT
43
43
  module ERC20
44
44
  # Current version of the gem (changed by the +.rultor.yml+ on every release)
45
- VERSION = '0.0.15'
45
+ VERSION = '0.0.17'
46
46
  end
@@ -43,7 +43,7 @@ class ERC20::FakeWallet
43
43
  @http_path = '/'
44
44
  end
45
45
 
46
- # Get balance of a public address.
46
+ # Get ERC20 balance of a public address.
47
47
  #
48
48
  # @param [String] _hex Public key, in hex, starting from '0x'
49
49
  # @return [Integer] Balance, in tokens
@@ -51,7 +51,33 @@ class ERC20::FakeWallet
51
51
  42_000_000
52
52
  end
53
53
 
54
- # Send a single payment from a private address to a public one.
54
+ # Get ETH balance of a public address.
55
+ #
56
+ # @param [String] _hex Public key, in hex, starting from '0x'
57
+ # @return [Integer] Balance, in tokens
58
+ def eth_balance(_hex)
59
+ 42_000_000
60
+ end
61
+
62
+ # How much ETH gas is required in order to send this ETH transaction.
63
+ #
64
+ # @param [String] _from The departing address, in hex
65
+ # @param [String] _to Arriving address, in hex
66
+ # @return [Integer] How many ETH required
67
+ def eth_gas_required(_from, _to)
68
+ 55_000
69
+ end
70
+
71
+ # How much ETH gas is required in order to send this ERC20 transaction.
72
+ #
73
+ # @param [String] _from The departing address, in hex
74
+ # @param [String] _to Arriving address, in hex
75
+ # @return [Integer] How many ETH required
76
+ def gas_required(_from, _to)
77
+ 66_000
78
+ end
79
+
80
+ # Send a single ERC20 payment from a private address to a public one.
55
81
  #
56
82
  # @param [String] _priv Private key, in hex
57
83
  # @param [String] _address Public key, in hex
@@ -61,6 +87,16 @@ class ERC20::FakeWallet
61
87
  '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
62
88
  end
63
89
 
90
+ # Send a single ETH payment from a private address to a public one.
91
+ #
92
+ # @param [String] _priv Private key, in hex
93
+ # @param [String] _address Public key, in hex
94
+ # @param [Integer] _amount The amount of ETHs to send
95
+ # @return [String] Transaction hash
96
+ def eth_pay(_priv, _address, _amount, *)
97
+ '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
98
+ end
99
+
64
100
  # Wait and accept.
65
101
  #
66
102
  # @param [Array<String>] addresses Addresses to monitor
data/lib/erc20/wallet.rb CHANGED
@@ -122,23 +122,62 @@ class ERC20::Wallet
122
122
  @mutex = Mutex.new
123
123
  end
124
124
 
125
- # Get balance of a public address.
125
+ # Get ERC20 balance of a public address (it's not the same as ETH balance!).
126
126
  #
127
- # @param [String] hex Public key, in hex, starting from '0x'
127
+ # An address in Etherium may have many balances. One of them is the main
128
+ # balance in ETH crypto. Another balance is the one kept by ERC20 contract
129
+ # in its own ledge in root storage. This balance is checked by this method.
130
+ #
131
+ # @param [String] address Public key, in hex, starting from '0x'
128
132
  # @return [Integer] Balance, in tokens
129
- def balance(hex)
130
- raise 'Address can\'t be nil' unless hex
131
- raise 'Address must be a String' unless hex.is_a?(String)
132
- raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(hex)
133
+ def balance(address)
134
+ raise 'Address can\'t be nil' unless address
135
+ raise 'Address must be a String' unless address.is_a?(String)
136
+ raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
133
137
  func = '70a08231' # balanceOf
134
- data = "0x#{func}000000000000000000000000#{hex[2..].downcase}"
138
+ data = "0x#{func}000000000000000000000000#{address[2..].downcase}"
135
139
  r = jsonrpc.eth_call({ to: @contract, data: data }, 'latest')
136
140
  b = r[2..].to_i(16)
137
- @log.debug("Balance of #{hex} is #{b}")
141
+ @log.debug("Balance of #{address} is #{b} ERC20 tokens")
138
142
  b
139
143
  end
140
144
 
141
- # Send a single payment from a private address to a public one.
145
+ # Get ETH balance of a public address.
146
+ #
147
+ # An address in Etherium may have many balances. One of them is the main
148
+ # balance in ETH crypto. This balance is checked by this method.
149
+ #
150
+ # @param [String] hex Public key, in hex, starting from '0x'
151
+ # @return [Integer] Balance, in ETH
152
+ def eth_balance(address)
153
+ raise 'Address can\'t be nil' unless address
154
+ raise 'Address must be a String' unless address.is_a?(String)
155
+ raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
156
+ r = jsonrpc.eth_getBalance(address, 'latest')
157
+ b = r[2..].to_i(16)
158
+ @log.debug("Balance of #{address} is #{b} ETHs")
159
+ b
160
+ end
161
+
162
+ # How much ETH gas is required in order to send this ETH transaction.
163
+ #
164
+ # @param [String] from The departing address, in hex
165
+ # @param [String] to Arriving address, in hex
166
+ # @return [Integer] How many ETH required
167
+ def eth_gas_required(from, to)
168
+ gas_estimate(from, to)
169
+ end
170
+
171
+ # How much ETH gas is required in order to send this ERC20 transaction.
172
+ #
173
+ # @param [String] from The departing address, in hex
174
+ # @param [String] to Arriving address, in hex
175
+ # @return [Integer] How many ETH required
176
+ def gas_required(from, to)
177
+ gas_estimate(from, to, to_pay_data(from, 100_000))
178
+ end
179
+
180
+ # Send a single ERC20 payment from a private address to a public one.
142
181
  #
143
182
  # @param [String] priv Private key, in hex
144
183
  # @param [String] address Public key, in hex
@@ -146,7 +185,7 @@ class ERC20::Wallet
146
185
  # @param [Integer] gas_limit How much gas you're ready to spend
147
186
  # @param [Integer] gas_price How much gas you pay per computation unit
148
187
  # @return [String] Transaction hash
149
- def pay(priv, address, amount, gas_limit: nil, gas_price: nil)
188
+ def pay(priv, address, amount, gas_limit: nil, gas_price: gas_best_price)
150
189
  raise 'Private key can\'t be nil' unless priv
151
190
  raise 'Private key must be a String' unless priv.is_a?(String)
152
191
  raise 'Invalid format of private key' unless /^[0-9a-fA-F]{64}$/.match?(priv)
@@ -164,25 +203,20 @@ class ERC20::Wallet
164
203
  raise 'Gas price must be an Integer' unless gas_price.is_a?(Integer)
165
204
  raise 'Gas price must be a positive Integer' unless gas_price.positive?
166
205
  end
167
- func = 'a9059cbb' # transfer(address,uint256)
168
- to_clean = address.downcase.sub(/^0x/, '')
169
- to_padded = ('0' * (64 - to_clean.size)) + to_clean
170
- amt_hex = amount.to_s(16)
171
- amt_padded = ('0' * (64 - amt_hex.size)) + amt_hex
172
- data = "0x#{func}#{to_padded}#{amt_padded}"
173
206
  key = Eth::Key.new(priv: priv)
174
207
  from = key.address.to_s
208
+ data = to_pay_data(address, amount)
175
209
  tnx =
176
210
  @mutex.synchronize do
177
211
  nonce = jsonrpc.eth_getTransactionCount(from, 'pending').to_i(16)
178
212
  tx = Eth::Tx.new(
179
213
  {
180
214
  nonce:,
181
- gas_price: gas_price || gas_best_price,
182
- gas_limit: gas_limit || gas_estimate(from, data),
215
+ gas_price: gas_price,
216
+ gas_limit: gas_limit || gas_estimate(from, @contract, data),
183
217
  to: @contract,
184
218
  value: 0,
185
- data: data,
219
+ data:,
186
220
  chain_id: @chain
187
221
  }
188
222
  )
@@ -190,7 +224,56 @@ class ERC20::Wallet
190
224
  hex = "0x#{tx.hex}"
191
225
  jsonrpc.eth_sendRawTransaction(hex)
192
226
  end
193
- @log.debug("Sent #{amount} from #{from} to #{address}: #{tnx}")
227
+ @log.debug("Sent #{amount} ERC20 tokens from #{from} to #{address}: #{tnx}")
228
+ tnx.downcase
229
+ end
230
+
231
+ # Send a single ETH payment from a private address to a public one.
232
+ #
233
+ # @param [String] priv Private key, in hex
234
+ # @param [String] address Public key, in hex
235
+ # @param [Integer] amount The amount of ERC20 tokens to send
236
+ # @param [Integer] gas_limit How much gas you're ready to spend
237
+ # @param [Integer] gas_price How much gas you pay per computation unit
238
+ # @return [String] Transaction hash
239
+ def eth_pay(priv, address, amount, gas_limit: nil, gas_price: gas_best_price)
240
+ raise 'Private key can\'t be nil' unless priv
241
+ raise 'Private key must be a String' unless priv.is_a?(String)
242
+ raise 'Invalid format of private key' unless /^[0-9a-fA-F]{64}$/.match?(priv)
243
+ raise 'Address can\'t be nil' unless address
244
+ raise 'Address must be a String' unless address.is_a?(String)
245
+ raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
246
+ raise 'Amount can\'t be nil' unless amount
247
+ raise 'Amount must be an Integer' unless amount.is_a?(Integer)
248
+ raise 'Amount must be a positive Integer' unless amount.positive?
249
+ if gas_limit
250
+ raise 'Gas limit must be an Integer' unless gas_limit.is_a?(Integer)
251
+ raise 'Gas limit must be a positive Integer' unless gas_limit.positive?
252
+ end
253
+ if gas_price
254
+ raise 'Gas price must be an Integer' unless gas_price.is_a?(Integer)
255
+ raise 'Gas price must be a positive Integer' unless gas_price.positive?
256
+ end
257
+ key = Eth::Key.new(priv: priv)
258
+ from = key.address.to_s
259
+ tnx =
260
+ @mutex.synchronize do
261
+ nonce = jsonrpc.eth_getTransactionCount(from, 'pending').to_i(16)
262
+ tx = Eth::Tx.new(
263
+ {
264
+ chain_id: @chain,
265
+ nonce:,
266
+ gas_price: gas_price,
267
+ gas_limit: gas_limit || gas_estimate(from, address),
268
+ to: address,
269
+ value: amount
270
+ }
271
+ )
272
+ tx.sign(key)
273
+ hex = "0x#{tx.hex}"
274
+ jsonrpc.eth_sendRawTransaction(hex)
275
+ end
276
+ @log.debug("Sent #{amount} ETHs from #{from} to #{address}: #{tnx}")
194
277
  tnx.downcase
195
278
  end
196
279
 
@@ -343,8 +426,20 @@ class ERC20::Wallet
343
426
  JSONRPC::Client.new(url, connection:)
344
427
  end
345
428
 
346
- def gas_estimate(from, data)
347
- jsonrpc.eth_estimateGas({ from:, to: @contract, data: }, 'latest').to_i(16)
429
+ # How much gas should be spent in order to send a transaction from one
430
+ # public address to another public address, possible carrying some data
431
+ # inside the transaction.
432
+ def gas_estimate(from, to, data = '')
433
+ jsonrpc.eth_estimateGas({ from:, to:, data: }, 'latest').to_i(16)
434
+ end
435
+
436
+ def to_pay_data(address, amount)
437
+ func = 'a9059cbb' # transfer(address,uint256)
438
+ to_clean = address.downcase.sub(/^0x/, '')
439
+ to_padded = ('0' * (64 - to_clean.size)) + to_clean
440
+ amt_hex = amount.to_s(16)
441
+ amt_padded = ('0' * (64 - amt_hex.size)) + amt_hex
442
+ "0x#{func}#{to_padded}#{amt_padded}"
348
443
  end
349
444
 
350
445
  def gas_best_price
@@ -33,20 +33,37 @@ require 'typhoeus'
33
33
  require_relative '../../lib/erc20/fake_wallet'
34
34
  require_relative '../test__helper'
35
35
 
36
- k = Eth::Key.new
37
- puts k.private_hex
38
- puts k.address
39
-
40
36
  # Test.
41
37
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
42
38
  # Copyright:: Copyright (c) 2025 Yegor Bugayenko
43
39
  # License:: MIT
44
40
  class TestFakeWallet < Minitest::Test
41
+ def test_checks_gas_required
42
+ b = ERC20::FakeWallet.new.gas_required(
43
+ '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff',
44
+ '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff'
45
+ )
46
+ refute_nil(b)
47
+ end
48
+
49
+ def test_checks_eth_gas_required
50
+ b = ERC20::FakeWallet.new.eth_gas_required(
51
+ '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff',
52
+ '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff'
53
+ )
54
+ refute_nil(b)
55
+ end
56
+
45
57
  def test_checks_fake_balance
46
58
  b = ERC20::FakeWallet.new.balance('0xEB2fE8872A6f1eDb70a2632Effffffffffffffff')
47
59
  refute_nil(b)
48
60
  end
49
61
 
62
+ def test_checks_fake_eth_balance
63
+ b = ERC20::FakeWallet.new.eth_balance('0xEB2fE8872A6f1eDb70a2632Effffffffffffffff')
64
+ refute_nil(b)
65
+ end
66
+
50
67
  def test_returns_host
51
68
  assert_equal('example.com', ERC20::FakeWallet.new.host)
52
69
  end
@@ -59,6 +76,14 @@ class TestFakeWallet < Minitest::Test
59
76
  assert_match(/^0x[a-f0-9]{64}$/, txn)
60
77
  end
61
78
 
79
+ def test_pays_fake_eths
80
+ priv = '81a9b2114d53731ecc84b261ef6c0387dde34d5907fe7b441240cc21d61bf80a'
81
+ to = '0xfadef8ba4a5d709a2bf55b7a8798c9b438c640c1'
82
+ txn = ERC20::FakeWallet.new.eth_pay(Eth::Key.new(priv:), to, 555)
83
+ assert_equal(66, txn.length)
84
+ assert_match(/^0x[a-f0-9]{64}$/, txn)
85
+ end
86
+
62
87
  def test_accepts_payments_on_hardhat
63
88
  active = Primitivo.new([])
64
89
  addresses = Primitivo.new(['0xfadef8ba4a5d709a2bf55b7a8798c9b438c640c1'])
@@ -38,7 +38,7 @@ require_relative '../test__helper'
38
38
  # Copyright:: Copyright (c) 2025 Yegor Bugayenko
39
39
  # License:: MIT
40
40
  class TestWallet < Minitest::Test
41
- # At this address, in Etherium mainnet, there are $8 USDT. I won't
41
+ # At this address, in Etherium mainnet, there are $8 USDT and 0.0042 ETH. I won't
42
42
  # move them anyway, that's why tests can use this address forever.
43
43
  STABLE = '0x7232148927F8a580053792f44D4d59d40Fd00ABD'
44
44
 
@@ -51,7 +51,13 @@ class TestWallet < Minitest::Test
51
51
  def test_checks_balance_on_mainnet
52
52
  b = mainnet.balance(STABLE)
53
53
  refute_nil(b)
54
- assert_equal(8_000_000, b)
54
+ assert_equal(8_000_000, b) # this is $8 USDT
55
+ end
56
+
57
+ def test_checks_eth_balance_on_mainnet
58
+ b = mainnet.eth_balance(STABLE)
59
+ refute_nil(b)
60
+ assert_equal(4_200_000_000_000_000, b) # this is 0.0042 ETH
55
61
  end
56
62
 
57
63
  def test_checks_balance_of_absent_address
@@ -61,6 +67,12 @@ class TestWallet < Minitest::Test
61
67
  assert_equal(0, b)
62
68
  end
63
69
 
70
+ def test_checks_gas_required_on_mainnet
71
+ b = mainnet.gas_required(STABLE, Eth::Key.new(priv: JEFF).address.to_s)
72
+ refute_nil(b)
73
+ assert_predicate(b, :positive?)
74
+ end
75
+
64
76
  def test_fails_with_invalid_infura_key
65
77
  skip('Apparently, even with invalid key, Infura returns balance')
66
78
  w = ERC20::Wallet.new(
@@ -88,6 +100,26 @@ class TestWallet < Minitest::Test
88
100
  assert_predicate(b, :zero?)
89
101
  end
90
102
 
103
+ def test_checks_gas_required_on_hardhat
104
+ on_hardhat do |wallet|
105
+ b = wallet.gas_required(
106
+ Eth::Key.new(priv: JEFF).address.to_s,
107
+ Eth::Key.new(priv: WALTER).address.to_s
108
+ )
109
+ assert_equal(21_597, b)
110
+ end
111
+ end
112
+
113
+ def test_checks_eth_gas_required_on_hardhat
114
+ on_hardhat do |wallet|
115
+ b = wallet.eth_gas_required(
116
+ Eth::Key.new(priv: JEFF).address.to_s,
117
+ Eth::Key.new(priv: WALTER).address.to_s
118
+ )
119
+ assert_equal(21_001, b)
120
+ end
121
+ end
122
+
91
123
  def test_checks_balance_on_hardhat
92
124
  on_hardhat do |wallet|
93
125
  b = wallet.balance(Eth::Key.new(priv: JEFF).address.to_s)
@@ -95,6 +127,13 @@ class TestWallet < Minitest::Test
95
127
  end
96
128
  end
97
129
 
130
+ def test_checks_eth_balance_on_hardhat
131
+ on_hardhat do |wallet|
132
+ b = wallet.balance(Eth::Key.new(priv: WALTER).address.to_s)
133
+ assert_equal(456_000_000_000, b)
134
+ end
135
+ end
136
+
98
137
  def test_checks_balance_on_hardhat_in_threads
99
138
  on_hardhat do |wallet|
100
139
  Threads.new.assert do
@@ -118,6 +157,20 @@ class TestWallet < Minitest::Test
118
157
  end
119
158
  end
120
159
 
160
+ def test_eth_pays_on_hardhat
161
+ on_hardhat do |wallet|
162
+ to = Eth::Key.new(priv: WALTER).address.to_s
163
+ before = wallet.eth_balance(to)
164
+ sum = 42_000
165
+ from = Eth::Key.new(priv: JEFF).address.to_s
166
+ assert_operator(wallet.eth_balance(from), :>, sum * 2)
167
+ txn = wallet.eth_pay(JEFF, to, sum)
168
+ assert_equal(66, txn.length)
169
+ assert_match(/^0x[a-f0-9]{64}$/, txn)
170
+ assert_equal(before + sum, wallet.eth_balance(to))
171
+ end
172
+ end
173
+
121
174
  def test_pays_on_hardhat_in_threads
122
175
  on_hardhat do |wallet|
123
176
  to = Eth::Key.new(priv: WALTER).address.to_s
@@ -131,6 +184,19 @@ class TestWallet < Minitest::Test
131
184
  end
132
185
  end
133
186
 
187
+ def test_pays_eth_on_hardhat_in_threads
188
+ on_hardhat do |wallet|
189
+ to = Eth::Key.new(priv: WALTER).address.to_s
190
+ before = wallet.eth_balance(to)
191
+ sum = 42_000
192
+ mul = 10
193
+ Threads.new(mul).assert do
194
+ wallet.eth_pay(JEFF, to, sum)
195
+ end
196
+ assert_equal(before + (sum * mul), wallet.eth_balance(to))
197
+ end
198
+ end
199
+
134
200
  def test_accepts_payments_on_hardhat
135
201
  walter = Eth::Key.new(priv: WALTER).address.to_s.downcase
136
202
  jeff = Eth::Key.new(priv: JEFF).address.to_s.downcase
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.0.15
4
+ version: 0.0.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko