erc20 0.0.16 → 0.0.17

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: 7d2dd0547d65ca59e601a28f93a2b35b45ae8b3dc57fe6a09bd296d13fe807fd
4
- data.tar.gz: a69c35dfd60d083f891a840285411373e7996937481f7c52a63f2ce9c09e2462
3
+ metadata.gz: 53d1ed4b12b1b7e295242f5345c3ebd4f22e8fe83af31bd2bcb4f27ec8a0b4ca
4
+ data.tar.gz: e377b3185614081e92f72a7fda5c9a8019de72a3497b9d28259e13f17a5f1ab0
5
5
  SHA512:
6
- metadata.gz: 1ddfb2214d688465488e224c59460e185b8bb361acadfd3b4d215053392f53dcf23367366b29a577b563be982432233efcc72ba5f4e6f6cae6651189b221c53c
7
- data.tar.gz: d8f93754ea6baf64d46591aa3ab52559bb601785df08d3071f51d5e283c7d0ad7c2b226bdaec12abbef913750136f5e895a302a451aa4ddac2225b862497631f
6
+ metadata.gz: 892709a9568e7837a708feefd8d7880d309fae9f921d1a8266e1b7b995acb1575a6305af92a1dd5dab7b9e7a7f3da9cc5b0b2539a9984eac91d291f59c5d86d1
7
+ data.tar.gz: d945d2ec483e4cc14ff76683294941cfaf9603963baf8c3acbf64189ee59aead1ec70e6b2d292c889d790c8f8f6fcd59c5327bb2a5223a138821fd5592971178
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.16'
45
+ VERSION = '0.0.17'
46
46
  end
@@ -59,6 +59,24 @@ class ERC20::FakeWallet
59
59
  42_000_000
60
60
  end
61
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
+
62
80
  # Send a single ERC20 payment from a private address to a public one.
63
81
  #
64
82
  # @param [String] _priv Private key, in hex
data/lib/erc20/wallet.rb CHANGED
@@ -159,6 +159,24 @@ class ERC20::Wallet
159
159
  b
160
160
  end
161
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
+
162
180
  # Send a single ERC20 payment from a private address to a public one.
163
181
  #
164
182
  # @param [String] priv Private key, in hex
@@ -167,7 +185,7 @@ class ERC20::Wallet
167
185
  # @param [Integer] gas_limit How much gas you're ready to spend
168
186
  # @param [Integer] gas_price How much gas you pay per computation unit
169
187
  # @return [String] Transaction hash
170
- 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)
171
189
  raise 'Private key can\'t be nil' unless priv
172
190
  raise 'Private key must be a String' unless priv.is_a?(String)
173
191
  raise 'Invalid format of private key' unless /^[0-9a-fA-F]{64}$/.match?(priv)
@@ -185,25 +203,20 @@ class ERC20::Wallet
185
203
  raise 'Gas price must be an Integer' unless gas_price.is_a?(Integer)
186
204
  raise 'Gas price must be a positive Integer' unless gas_price.positive?
187
205
  end
188
- func = 'a9059cbb' # transfer(address,uint256)
189
- to_clean = address.downcase.sub(/^0x/, '')
190
- to_padded = ('0' * (64 - to_clean.size)) + to_clean
191
- amt_hex = amount.to_s(16)
192
- amt_padded = ('0' * (64 - amt_hex.size)) + amt_hex
193
- data = "0x#{func}#{to_padded}#{amt_padded}"
194
206
  key = Eth::Key.new(priv: priv)
195
207
  from = key.address.to_s
208
+ data = to_pay_data(address, amount)
196
209
  tnx =
197
210
  @mutex.synchronize do
198
211
  nonce = jsonrpc.eth_getTransactionCount(from, 'pending').to_i(16)
199
212
  tx = Eth::Tx.new(
200
213
  {
201
214
  nonce:,
202
- gas_price: gas_price || gas_best_price,
215
+ gas_price: gas_price,
203
216
  gas_limit: gas_limit || gas_estimate(from, @contract, data),
204
217
  to: @contract,
205
218
  value: 0,
206
- data: data,
219
+ data:,
207
220
  chain_id: @chain
208
221
  }
209
222
  )
@@ -223,7 +236,7 @@ class ERC20::Wallet
223
236
  # @param [Integer] gas_limit How much gas you're ready to spend
224
237
  # @param [Integer] gas_price How much gas you pay per computation unit
225
238
  # @return [String] Transaction hash
226
- def eth_pay(priv, address, amount, gas_limit: nil, gas_price: nil)
239
+ def eth_pay(priv, address, amount, gas_limit: nil, gas_price: gas_best_price)
227
240
  raise 'Private key can\'t be nil' unless priv
228
241
  raise 'Private key must be a String' unless priv.is_a?(String)
229
242
  raise 'Invalid format of private key' unless /^[0-9a-fA-F]{64}$/.match?(priv)
@@ -243,7 +256,6 @@ class ERC20::Wallet
243
256
  end
244
257
  key = Eth::Key.new(priv: priv)
245
258
  from = key.address.to_s
246
- data = ''
247
259
  tnx =
248
260
  @mutex.synchronize do
249
261
  nonce = jsonrpc.eth_getTransactionCount(from, 'pending').to_i(16)
@@ -251,11 +263,10 @@ class ERC20::Wallet
251
263
  {
252
264
  chain_id: @chain,
253
265
  nonce:,
254
- gas_price: gas_price || gas_best_price,
255
- gas_limit: gas_limit || gas_estimate(from, address, data),
266
+ gas_price: gas_price,
267
+ gas_limit: gas_limit || gas_estimate(from, address),
256
268
  to: address,
257
- value: amount,
258
- data:
269
+ value: amount
259
270
  }
260
271
  )
261
272
  tx.sign(key)
@@ -415,10 +426,22 @@ class ERC20::Wallet
415
426
  JSONRPC::Client.new(url, connection:)
416
427
  end
417
428
 
418
- def gas_estimate(from, to, data)
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 = '')
419
433
  jsonrpc.eth_estimateGas({ from:, to:, data: }, 'latest').to_i(16)
420
434
  end
421
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}"
443
+ end
444
+
422
445
  def gas_best_price
423
446
  jsonrpc.eth_getBlockByNumber('latest', false)['baseFeePerGas'].to_i(16)
424
447
  end
@@ -38,6 +38,22 @@ require_relative '../test__helper'
38
38
  # Copyright:: Copyright (c) 2025 Yegor Bugayenko
39
39
  # License:: MIT
40
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
+
41
57
  def test_checks_fake_balance
42
58
  b = ERC20::FakeWallet.new.balance('0xEB2fE8872A6f1eDb70a2632Effffffffffffffff')
43
59
  refute_nil(b)
@@ -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,13 +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
55
  end
56
56
 
57
57
  def test_checks_eth_balance_on_mainnet
58
58
  b = mainnet.eth_balance(STABLE)
59
59
  refute_nil(b)
60
- assert_equal(0, b)
60
+ assert_equal(4_200_000_000_000_000, b) # this is 0.0042 ETH
61
61
  end
62
62
 
63
63
  def test_checks_balance_of_absent_address
@@ -67,6 +67,12 @@ class TestWallet < Minitest::Test
67
67
  assert_equal(0, b)
68
68
  end
69
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
+
70
76
  def test_fails_with_invalid_infura_key
71
77
  skip('Apparently, even with invalid key, Infura returns balance')
72
78
  w = ERC20::Wallet.new(
@@ -94,6 +100,26 @@ class TestWallet < Minitest::Test
94
100
  assert_predicate(b, :zero?)
95
101
  end
96
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
+
97
123
  def test_checks_balance_on_hardhat
98
124
  on_hardhat do |wallet|
99
125
  b = wallet.balance(Eth::Key.new(priv: JEFF).address.to_s)
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.16
4
+ version: 0.0.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko