erc20 0.0.15 → 0.0.16

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: 9aaaf16c1cb37a5f676efbec190e3b707c34fa816e781d3119c43b5b2b44500b
4
- data.tar.gz: a77d26b7f77b56f9a46f860b4a5e5104ed7239da40f16d14b056c96b8d05ca88
3
+ metadata.gz: 7d2dd0547d65ca59e601a28f93a2b35b45ae8b3dc57fe6a09bd296d13fe807fd
4
+ data.tar.gz: a69c35dfd60d083f891a840285411373e7996937481f7c52a63f2ce9c09e2462
5
5
  SHA512:
6
- metadata.gz: edef3ec85bb02e73d8815907bf6f256b1a261b8051ada8a1280efde631d8faaa70e5533c4577e63843615a163e4115e383ce4d7d891592277fb71377d1eafffd
7
- data.tar.gz: 484da28c8385d9a5b4ce18ca0cffd26e221144d641d1e9f777980a6394edcac0843a9f5e58e2243e7f858aea561ff8a39466934eefd01cfebced3071a029f24b
6
+ metadata.gz: 1ddfb2214d688465488e224c59460e185b8bb361acadfd3b4d215053392f53dcf23367366b29a577b563be982432233efcc72ba5f4e6f6cae6651189b221c53c
7
+ data.tar.gz: d8f93754ea6baf64d46591aa3ab52559bb601785df08d3071f51d5e283c7d0ad7c2b226bdaec12abbef913750136f5e895a302a451aa4ddac2225b862497631f
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.16'
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,15 @@ 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
+ # Send a single ERC20 payment from a private address to a public one.
55
63
  #
56
64
  # @param [String] _priv Private key, in hex
57
65
  # @param [String] _address Public key, in hex
@@ -61,6 +69,16 @@ class ERC20::FakeWallet
61
69
  '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
62
70
  end
63
71
 
72
+ # Send a single ETH payment from a private address to a public one.
73
+ #
74
+ # @param [String] _priv Private key, in hex
75
+ # @param [String] _address Public key, in hex
76
+ # @param [Integer] _amount The amount of ETHs to send
77
+ # @return [String] Transaction hash
78
+ def eth_pay(_priv, _address, _amount, *)
79
+ '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
80
+ end
81
+
64
82
  # Wait and accept.
65
83
  #
66
84
  # @param [Array<String>] addresses Addresses to monitor
data/lib/erc20/wallet.rb CHANGED
@@ -122,23 +122,44 @@ 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")
142
+ b
143
+ end
144
+
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")
138
159
  b
139
160
  end
140
161
 
141
- # Send a single payment from a private address to a public one.
162
+ # Send a single ERC20 payment from a private address to a public one.
142
163
  #
143
164
  # @param [String] priv Private key, in hex
144
165
  # @param [String] address Public key, in hex
@@ -179,7 +200,7 @@ class ERC20::Wallet
179
200
  {
180
201
  nonce:,
181
202
  gas_price: gas_price || gas_best_price,
182
- gas_limit: gas_limit || gas_estimate(from, data),
203
+ gas_limit: gas_limit || gas_estimate(from, @contract, data),
183
204
  to: @contract,
184
205
  value: 0,
185
206
  data: data,
@@ -190,7 +211,58 @@ class ERC20::Wallet
190
211
  hex = "0x#{tx.hex}"
191
212
  jsonrpc.eth_sendRawTransaction(hex)
192
213
  end
193
- @log.debug("Sent #{amount} from #{from} to #{address}: #{tnx}")
214
+ @log.debug("Sent #{amount} ERC20 tokens from #{from} to #{address}: #{tnx}")
215
+ tnx.downcase
216
+ end
217
+
218
+ # Send a single ETH payment from a private address to a public one.
219
+ #
220
+ # @param [String] priv Private key, in hex
221
+ # @param [String] address Public key, in hex
222
+ # @param [Integer] amount The amount of ERC20 tokens to send
223
+ # @param [Integer] gas_limit How much gas you're ready to spend
224
+ # @param [Integer] gas_price How much gas you pay per computation unit
225
+ # @return [String] Transaction hash
226
+ def eth_pay(priv, address, amount, gas_limit: nil, gas_price: nil)
227
+ raise 'Private key can\'t be nil' unless priv
228
+ raise 'Private key must be a String' unless priv.is_a?(String)
229
+ raise 'Invalid format of private key' unless /^[0-9a-fA-F]{64}$/.match?(priv)
230
+ raise 'Address can\'t be nil' unless address
231
+ raise 'Address must be a String' unless address.is_a?(String)
232
+ raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
233
+ raise 'Amount can\'t be nil' unless amount
234
+ raise 'Amount must be an Integer' unless amount.is_a?(Integer)
235
+ raise 'Amount must be a positive Integer' unless amount.positive?
236
+ if gas_limit
237
+ raise 'Gas limit must be an Integer' unless gas_limit.is_a?(Integer)
238
+ raise 'Gas limit must be a positive Integer' unless gas_limit.positive?
239
+ end
240
+ if gas_price
241
+ raise 'Gas price must be an Integer' unless gas_price.is_a?(Integer)
242
+ raise 'Gas price must be a positive Integer' unless gas_price.positive?
243
+ end
244
+ key = Eth::Key.new(priv: priv)
245
+ from = key.address.to_s
246
+ data = ''
247
+ tnx =
248
+ @mutex.synchronize do
249
+ nonce = jsonrpc.eth_getTransactionCount(from, 'pending').to_i(16)
250
+ tx = Eth::Tx.new(
251
+ {
252
+ chain_id: @chain,
253
+ nonce:,
254
+ gas_price: gas_price || gas_best_price,
255
+ gas_limit: gas_limit || gas_estimate(from, address, data),
256
+ to: address,
257
+ value: amount,
258
+ data:
259
+ }
260
+ )
261
+ tx.sign(key)
262
+ hex = "0x#{tx.hex}"
263
+ jsonrpc.eth_sendRawTransaction(hex)
264
+ end
265
+ @log.debug("Sent #{amount} ETHs from #{from} to #{address}: #{tnx}")
194
266
  tnx.downcase
195
267
  end
196
268
 
@@ -343,8 +415,8 @@ class ERC20::Wallet
343
415
  JSONRPC::Client.new(url, connection:)
344
416
  end
345
417
 
346
- def gas_estimate(from, data)
347
- jsonrpc.eth_estimateGas({ from:, to: @contract, data: }, 'latest').to_i(16)
418
+ def gas_estimate(from, to, data)
419
+ jsonrpc.eth_estimateGas({ from:, to:, data: }, 'latest').to_i(16)
348
420
  end
349
421
 
350
422
  def gas_best_price
@@ -33,10 +33,6 @@ 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
@@ -47,6 +43,11 @@ class TestFakeWallet < Minitest::Test
47
43
  refute_nil(b)
48
44
  end
49
45
 
46
+ def test_checks_fake_eth_balance
47
+ b = ERC20::FakeWallet.new.eth_balance('0xEB2fE8872A6f1eDb70a2632Effffffffffffffff')
48
+ refute_nil(b)
49
+ end
50
+
50
51
  def test_returns_host
51
52
  assert_equal('example.com', ERC20::FakeWallet.new.host)
52
53
  end
@@ -59,6 +60,14 @@ class TestFakeWallet < Minitest::Test
59
60
  assert_match(/^0x[a-f0-9]{64}$/, txn)
60
61
  end
61
62
 
63
+ def test_pays_fake_eths
64
+ priv = '81a9b2114d53731ecc84b261ef6c0387dde34d5907fe7b441240cc21d61bf80a'
65
+ to = '0xfadef8ba4a5d709a2bf55b7a8798c9b438c640c1'
66
+ txn = ERC20::FakeWallet.new.eth_pay(Eth::Key.new(priv:), to, 555)
67
+ assert_equal(66, txn.length)
68
+ assert_match(/^0x[a-f0-9]{64}$/, txn)
69
+ end
70
+
62
71
  def test_accepts_payments_on_hardhat
63
72
  active = Primitivo.new([])
64
73
  addresses = Primitivo.new(['0xfadef8ba4a5d709a2bf55b7a8798c9b438c640c1'])
@@ -54,6 +54,12 @@ class TestWallet < Minitest::Test
54
54
  assert_equal(8_000_000, b)
55
55
  end
56
56
 
57
+ def test_checks_eth_balance_on_mainnet
58
+ b = mainnet.eth_balance(STABLE)
59
+ refute_nil(b)
60
+ assert_equal(0, b)
61
+ end
62
+
57
63
  def test_checks_balance_of_absent_address
58
64
  a = '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff'
59
65
  b = mainnet.balance(a)
@@ -95,6 +101,13 @@ class TestWallet < Minitest::Test
95
101
  end
96
102
  end
97
103
 
104
+ def test_checks_eth_balance_on_hardhat
105
+ on_hardhat do |wallet|
106
+ b = wallet.balance(Eth::Key.new(priv: WALTER).address.to_s)
107
+ assert_equal(456_000_000_000, b)
108
+ end
109
+ end
110
+
98
111
  def test_checks_balance_on_hardhat_in_threads
99
112
  on_hardhat do |wallet|
100
113
  Threads.new.assert do
@@ -118,6 +131,20 @@ class TestWallet < Minitest::Test
118
131
  end
119
132
  end
120
133
 
134
+ def test_eth_pays_on_hardhat
135
+ on_hardhat do |wallet|
136
+ to = Eth::Key.new(priv: WALTER).address.to_s
137
+ before = wallet.eth_balance(to)
138
+ sum = 42_000
139
+ from = Eth::Key.new(priv: JEFF).address.to_s
140
+ assert_operator(wallet.eth_balance(from), :>, sum * 2)
141
+ txn = wallet.eth_pay(JEFF, to, sum)
142
+ assert_equal(66, txn.length)
143
+ assert_match(/^0x[a-f0-9]{64}$/, txn)
144
+ assert_equal(before + sum, wallet.eth_balance(to))
145
+ end
146
+ end
147
+
121
148
  def test_pays_on_hardhat_in_threads
122
149
  on_hardhat do |wallet|
123
150
  to = Eth::Key.new(priv: WALTER).address.to_s
@@ -131,6 +158,19 @@ class TestWallet < Minitest::Test
131
158
  end
132
159
  end
133
160
 
161
+ def test_pays_eth_on_hardhat_in_threads
162
+ on_hardhat do |wallet|
163
+ to = Eth::Key.new(priv: WALTER).address.to_s
164
+ before = wallet.eth_balance(to)
165
+ sum = 42_000
166
+ mul = 10
167
+ Threads.new(mul).assert do
168
+ wallet.eth_pay(JEFF, to, sum)
169
+ end
170
+ assert_equal(before + (sum * mul), wallet.eth_balance(to))
171
+ end
172
+ end
173
+
134
174
  def test_accepts_payments_on_hardhat
135
175
  walter = Eth::Key.new(priv: WALTER).address.to_s.downcase
136
176
  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.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko