erc20 0.0.16 → 0.0.18

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: 7d2dd0547d65ca59e601a28f93a2b35b45ae8b3dc57fe6a09bd296d13fe807fd
4
- data.tar.gz: a69c35dfd60d083f891a840285411373e7996937481f7c52a63f2ce9c09e2462
3
+ metadata.gz: 37aa39344c429cfd2153f004bd2a92730cd3a22c4f11c04e31573d36a98b2045
4
+ data.tar.gz: c64d6baecf8256cce26bad81f16608b01098b1c581a1370dedb05b9761a8a6e2
5
5
  SHA512:
6
- metadata.gz: 1ddfb2214d688465488e224c59460e185b8bb361acadfd3b4d215053392f53dcf23367366b29a577b563be982432233efcc72ba5f4e6f6cae6651189b221c53c
7
- data.tar.gz: d8f93754ea6baf64d46591aa3ab52559bb601785df08d3071f51d5e283c7d0ad7c2b226bdaec12abbef913750136f5e895a302a451aa4ddac2225b862497631f
6
+ metadata.gz: 030bd6e0734bef65820b1c0a63237eed014bc1e1291e7a98f2e8ffe17bffab6aaa3d6cc9685f0901002512376c6ac2bece5d9c564249259700df82ba2bb609c0
7
+ data.tar.gz: 5836008fa63ea04bc68f7e8cc670f18b17ec887be606fedf1315e1d6e94d6be2b21859693c173d7ecd56e173fcd13f4598e08099fca941fb1bedd6367dc16e80
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.18'
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 ERC20 transaction.
163
+ #
164
+ # @param [String] from The departing address, in hex
165
+ # @param [String] to Arriving address, in hex (it's OK to skip it)
166
+ # @return [Integer] How many ETH required
167
+ def gas_required(from, to = from)
168
+ gas_estimate(from, to, to_pay_data(from, 100_000))
169
+ end
170
+
171
+ # How much ETH gas is required in order to send this ETH transaction.
172
+ #
173
+ # @param [String] from The departing address, in hex
174
+ # @param [String] to Arriving address, in hex (it's OK to skip it)
175
+ # @return [Integer] How many ETH required
176
+ def eth_gas_required(from, to = from)
177
+ gas_estimate(from, to)
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,8 +426,22 @@ class ERC20::Wallet
415
426
  JSONRPC::Client.new(url, connection:)
416
427
  end
417
428
 
418
- def gas_estimate(from, to, data)
419
- jsonrpc.eth_estimateGas({ from:, to:, 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
+ gas = jsonrpc.eth_estimateGas({ from:, to:, data: }, 'latest').to_i(16)
434
+ @log.debug("Estimated gas is #{gas} ETH#{data.empty? ? '' : ', for ERC20 transfer'}")
435
+ gas
436
+ end
437
+
438
+ def to_pay_data(address, amount)
439
+ func = 'a9059cbb' # transfer(address,uint256)
440
+ to_clean = address.downcase.sub(/^0x/, '')
441
+ to_padded = ('0' * (64 - to_clean.size)) + to_clean
442
+ amt_hex = amount.to_s(16)
443
+ amt_padded = ('0' * (64 - amt_hex.size)) + amt_hex
444
+ "0x#{func}#{to_padded}#{amt_padded}"
420
445
  end
421
446
 
422
447
  def gas_best_price
@@ -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,18 @@ 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
+
76
+ def test_checks_same_address_gas_required_on_mainnet
77
+ b = mainnet.gas_required(STABLE, STABLE)
78
+ refute_nil(b)
79
+ assert_predicate(b, :positive?)
80
+ end
81
+
70
82
  def test_fails_with_invalid_infura_key
71
83
  skip('Apparently, even with invalid key, Infura returns balance')
72
84
  w = ERC20::Wallet.new(
@@ -94,6 +106,31 @@ class TestWallet < Minitest::Test
94
106
  assert_predicate(b, :zero?)
95
107
  end
96
108
 
109
+ def test_checks_gas_required_on_hardhat
110
+ on_hardhat do |wallet|
111
+ b1 = wallet.gas_required(
112
+ Eth::Key.new(priv: JEFF).address.to_s,
113
+ Eth::Key.new(priv: WALTER).address.to_s
114
+ )
115
+ assert_equal(21_597, b1)
116
+ b2 = wallet.gas_required(
117
+ Eth::Key.new(priv: JEFF).address.to_s,
118
+ Eth::Key.new(priv: JEFF).address.to_s
119
+ )
120
+ assert_equal(b1, b2)
121
+ end
122
+ end
123
+
124
+ def test_checks_eth_gas_required_on_hardhat
125
+ on_hardhat do |wallet|
126
+ b = wallet.eth_gas_required(
127
+ Eth::Key.new(priv: JEFF).address.to_s,
128
+ Eth::Key.new(priv: WALTER).address.to_s
129
+ )
130
+ assert_equal(21_001, b)
131
+ end
132
+ end
133
+
97
134
  def test_checks_balance_on_hardhat
98
135
  on_hardhat do |wallet|
99
136
  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.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko