erc20 0.0.19 → 0.0.21

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.
data/hardhat/Dockerfile CHANGED
@@ -1,22 +1,5 @@
1
- # Copyright (c) 2025 Yegor Bugayenko
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the 'Software'), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in all
11
- # copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
- # SOFTWARE.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
20
3
 
21
4
  FROM node:22
22
5
 
@@ -1,4 +1,6 @@
1
+ // SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
1
2
  // SPDX-License-Identifier: MIT
3
+
2
4
  pragma solidity ^0.8.28;
3
5
 
4
6
  contract Foo {
@@ -1,22 +1,5 @@
1
- // Copyright (c) 2025 Yegor Bugayenko
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the "Software"), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in all
11
- // copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
- // SOFTWARE.
1
+ // SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
2
+ // SPDX-License-Identifier: MIT
20
3
 
21
4
  require("@nomicfoundation/hardhat-toolbox");
22
5
 
@@ -1,22 +1,5 @@
1
- // Copyright (c) 2025 Yegor Bugayenko
2
- //
3
- // Permission is hereby granted, free of charge, to any person obtaining a copy
4
- // of this software and associated documentation files (the 'Software'), to deal
5
- // in the Software without restriction, including without limitation the rights
6
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- // copies of the Software, and to permit persons to whom the Software is
8
- // furnished to do so, subject to the following conditions:
9
- //
10
- // The above copyright notice and this permission notice shall be included in all
11
- // copies or substantial portions of the Software.
12
- //
13
- // THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
16
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
- // SOFTWARE.
1
+ // SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
2
+ // SPDX-License-Identifier: MIT
20
3
 
21
4
  import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
22
5
 
data/lib/erc20/erc20.rb CHANGED
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2025 Yegor Bugayenko
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the 'Software'), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- # SOFTWARE.
3
+ # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
22
5
 
23
6
  # This module makes manipulations with Etherium ERC20 tokens
24
7
  # as simple as they can be, if you have a provider of
@@ -42,5 +25,5 @@
42
25
  # License:: MIT
43
26
  module ERC20
44
27
  # Current version of the gem (changed by the +.rultor.yml+ on every release)
45
- VERSION = '0.0.19'
28
+ VERSION = '0.0.21'
46
29
  end
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2025 Yegor Bugayenko
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the 'Software'), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- # SOFTWARE.
3
+ # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
22
5
 
23
6
  require_relative 'erc20'
24
7
  require_relative 'wallet'
@@ -29,6 +12,9 @@ require_relative 'wallet'
29
12
  # Copyright:: Copyright (c) 2025 Yegor Bugayenko
30
13
  # License:: MIT
31
14
  class ERC20::FakeWallet
15
+ # Transaction hash always returned:
16
+ TXN_HASH = '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
17
+
32
18
  # Fakes:
33
19
  attr_reader :host, :port, :ssl, :chain, :contract, :ws_path, :http_path
34
20
 
@@ -45,6 +31,22 @@ class ERC20::FakeWallet
45
31
  @ws_path = '/'
46
32
  @http_path = '/'
47
33
  @history = []
34
+ @balances = {}
35
+ @eth_balances = {}
36
+ end
37
+
38
+ # Set balance, to be returned by the +balance()+.
39
+ # @param [String] address Public key, in hex, starting from '0x'
40
+ # @param [Integer] tokens How many tokens to put there
41
+ def set_balance(address, tokens)
42
+ @balances[address] = tokens
43
+ end
44
+
45
+ # Set balance, to be returned by the +balance()+.
46
+ # @param [String] address Public key, in hex, starting from '0x'
47
+ # @param [Integer] wei How many wei to put there
48
+ def set_eth_balance(address, wei)
49
+ @eth_balances[address] = wei
48
50
  end
49
51
 
50
52
  # Get ERC20 balance of a public address.
@@ -52,7 +54,7 @@ class ERC20::FakeWallet
52
54
  # @param [String] address Public key, in hex, starting from '0x'
53
55
  # @return [Integer] Balance, in tokens
54
56
  def balance(address)
55
- b = 42_000_000
57
+ b = @balances[address] || 42_000_000
56
58
  @history << { method: :balance, address:, result: b }
57
59
  b
58
60
  end
@@ -62,31 +64,29 @@ class ERC20::FakeWallet
62
64
  # @param [String] address Public key, in hex, starting from '0x'
63
65
  # @return [Integer] Balance, in tokens
64
66
  def eth_balance(address)
65
- b = 77_000_000_000_000_000
67
+ b = @eth_balances[address] || 77_000_000_000_000_000
66
68
  @history << { method: :eth_balance, address:, result: b }
67
69
  b
68
70
  end
69
71
 
70
- # How much ETH gas is required in order to send this ERC20 transaction.
72
+ # How much gas units is required in order to send ERC20 transaction.
71
73
  #
72
74
  # @param [String] from The departing address, in hex
73
75
  # @param [String] to Arriving address, in hex
74
- # @return [Integer] How many ETH required
75
- def gas_required(from, to = from)
76
- g = 66_000
77
- @history << { method: :gas_required, from:, to:, result: g }
78
- g
76
+ # @param [Integer] amount How many ERC20 tokens to send
77
+ # @return [Integer] How many gas units required
78
+ def gas_estimate(from, to, amount)
79
+ gas = 66_000
80
+ @history << { method: :gas_estimate, from:, to:, amount:, result: gas }
81
+ gas
79
82
  end
80
83
 
81
- # How much ETH gas is required in order to send this ETH transaction.
82
- #
83
- # @param [String] from The departing address, in hex
84
- # @param [String] to Arriving address, in hex (may be skipped)
85
- # @return [Integer] How many ETH required
86
- def eth_gas_required(from, to = from)
87
- g = 55_000
88
- @history << { method: :eth_gas_required, from:, to:, result: g }
89
- g
84
+ # What is the price of gas unit in gwei?
85
+ # @return [Integer] Price of gas unit, in gwei (0.000000001 ETH)
86
+ def gas_price
87
+ gwei = 55_555
88
+ @history << { method: :gas_price, result: gwei }
89
+ gwei
90
90
  end
91
91
 
92
92
  # Send a single ERC20 payment from a private address to a public one.
@@ -96,7 +96,7 @@ class ERC20::FakeWallet
96
96
  # @param [Integer] _amount The amount of ERC20 tokens to send
97
97
  # @return [String] Transaction hash
98
98
  def pay(priv, address, amount, gas_limit: nil, gas_price: nil)
99
- hex = '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
99
+ hex = TXN_HASH
100
100
  @history << { method: :pay, priv:, address:, amount:, gas_limit:, gas_price:, result: hex }
101
101
  hex
102
102
  end
@@ -107,9 +107,9 @@ class ERC20::FakeWallet
107
107
  # @param [String] address Public key, in hex
108
108
  # @param [Integer] amount The amount of ETHs to send
109
109
  # @return [String] Transaction hash
110
- def eth_pay(priv, address, amount, gas_limit: nil, gas_price: nil)
111
- hex = '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
112
- @history << { method: :eth_pay, priv:, address:, amount:, gas_limit:, gas_price:, result: hex }
110
+ def eth_pay(priv, address, amount, gas_price: nil)
111
+ hex = TXN_HASH
112
+ @history << { method: :eth_pay, priv:, address:, amount:, gas_price:, result: hex }
113
113
  hex
114
114
  end
115
115
 
@@ -134,7 +134,7 @@ class ERC20::FakeWallet
134
134
  amount: 424_242,
135
135
  from: '0xd5ff1bfcde7a03da61ad229d962c74f1ea2f16a5',
136
136
  to: a,
137
- txn: '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
137
+ txn: TXN_HASH
138
138
  }
139
139
  end
140
140
  yield event
data/lib/erc20/wallet.rb CHANGED
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2025 Yegor Bugayenko
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the 'Software'), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- # SOFTWARE.
3
+ # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
22
5
 
23
6
  require 'eth'
24
7
  require 'eventmachine'
@@ -159,34 +142,33 @@ class ERC20::Wallet
159
142
  b
160
143
  end
161
144
 
162
- # How much ETH gas is required in order to send ERC20 transaction.
145
+ # How much gas units is required in order to send ERC20 transaction.
163
146
  #
164
147
  # @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)
148
+ # @param [String] to Arriving address, in hex
149
+ # @param [Integer] amount How many ERC20 tokens to send
150
+ # @return [Integer] How many gas units required
151
+ def gas_estimate(from, to, amount)
168
152
  raise 'Address can\'t be nil' unless from
169
153
  raise 'Address must be a String' unless from.is_a?(String)
170
154
  raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(from)
171
155
  raise 'Address can\'t be nil' unless to
172
156
  raise 'Address must be a String' unless to.is_a?(String)
173
157
  raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(to)
174
- gas_estimate(from, to, to_pay_data(from, 100_000))
158
+ raise 'Amount can\'t be nil' unless amount
159
+ raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
160
+ raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
161
+ gas = jsonrpc.eth_estimateGas({ from:, to: @contract, data: to_pay_data(to, amount) }, 'latest').to_i(16)
162
+ @log.debug("It would take #{gas} gas units to send #{amount} tokens from #{from} to #{to}")
163
+ gas
175
164
  end
176
165
 
177
- # How much ETH gas is required in order to send this ETH transaction.
178
- #
179
- # @param [String] from The departing address, in hex
180
- # @param [String] to Arriving address, in hex (it's OK to skip it)
181
- # @return [Integer] How many ETH required
182
- def eth_gas_required(from, to = from)
183
- raise 'Address can\'t be nil' unless from
184
- raise 'Address must be a String' unless from.is_a?(String)
185
- raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(from)
186
- raise 'Address can\'t be nil' unless to
187
- raise 'Address must be a String' unless to.is_a?(String)
188
- raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(to)
189
- gas_estimate(from, to)
166
+ # What is the price of gas unit in gwei?
167
+ # @return [Integer] Price of gas unit, in gwei (0.000000001 ETH)
168
+ def gas_price
169
+ gwei = jsonrpc.eth_getBlockByNumber('latest', false)['baseFeePerGas'].to_i(16)
170
+ @log.debug("The cost of one gas unit is #{gwei} gwei")
171
+ gwei
190
172
  end
191
173
 
192
174
  # Send a single ERC20 payment from a private address to a public one.
@@ -194,10 +176,10 @@ class ERC20::Wallet
194
176
  # @param [String] priv Private key, in hex
195
177
  # @param [String] address Public key, in hex
196
178
  # @param [Integer] amount The amount of ERC20 tokens to send
197
- # @param [Integer] gas_limit How much gas you're ready to spend
198
- # @param [Integer] gas_price How much gas you pay per computation unit
179
+ # @param [Integer] limit How much gas you're ready to spend
180
+ # @param [Integer] price How much gas you pay per computation unit
199
181
  # @return [String] Transaction hash
200
- def pay(priv, address, amount, gas_limit: nil, gas_price: gas_best_price)
182
+ def pay(priv, address, amount, limit: nil, price: gas_price)
201
183
  raise 'Private key can\'t be nil' unless priv
202
184
  raise 'Private key must be a String' unless priv.is_a?(String)
203
185
  raise 'Invalid format of private key' unless /^[0-9a-fA-F]{64}$/.match?(priv)
@@ -205,30 +187,29 @@ class ERC20::Wallet
205
187
  raise 'Address must be a String' unless address.is_a?(String)
206
188
  raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
207
189
  raise 'Amount can\'t be nil' unless amount
208
- raise 'Amount must be an Integer' unless amount.is_a?(Integer)
209
- raise 'Amount must be a positive Integer' unless amount.positive?
210
- if gas_limit
211
- raise 'Gas limit must be an Integer' unless gas_limit.is_a?(Integer)
212
- raise 'Gas limit must be a positive Integer' unless gas_limit.positive?
190
+ raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
191
+ raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
192
+ if limit
193
+ raise 'Gas limit must be an Integer' unless limit.is_a?(Integer)
194
+ raise 'Gas limit must be a positive Integer' unless limit.positive?
213
195
  end
214
- if gas_price
215
- raise 'Gas price must be an Integer' unless gas_price.is_a?(Integer)
216
- raise 'Gas price must be a positive Integer' unless gas_price.positive?
196
+ if price
197
+ raise 'Gas price must be an Integer' unless price.is_a?(Integer)
198
+ raise 'Gas price must be a positive Integer' unless price.positive?
217
199
  end
218
200
  key = Eth::Key.new(priv: priv)
219
201
  from = key.address.to_s
220
- data = to_pay_data(address, amount)
221
202
  tnx =
222
203
  @mutex.synchronize do
223
204
  nonce = jsonrpc.eth_getTransactionCount(from, 'pending').to_i(16)
224
205
  tx = Eth::Tx.new(
225
206
  {
226
207
  nonce:,
227
- gas_price: gas_price,
228
- gas_limit: gas_limit || gas_estimate(from, @contract, data),
208
+ gas_price: price,
209
+ gas_limit: limit || gas_estimate(from, address, amount),
229
210
  to: @contract,
230
211
  value: 0,
231
- data:,
212
+ data: to_pay_data(address, amount),
232
213
  chain_id: @chain
233
214
  }
234
215
  )
@@ -245,10 +226,10 @@ class ERC20::Wallet
245
226
  # @param [String] priv Private key, in hex
246
227
  # @param [String] address Public key, in hex
247
228
  # @param [Integer] amount The amount of ERC20 tokens to send
248
- # @param [Integer] gas_limit How much gas you're ready to spend
249
- # @param [Integer] gas_price How much gas you pay per computation unit
229
+ # @param [Integer] limit How much gas you're ready to spend
230
+ # @param [Integer] price How much gas you pay per computation unit
250
231
  # @return [String] Transaction hash
251
- def eth_pay(priv, address, amount, gas_limit: nil, gas_price: gas_best_price)
232
+ def eth_pay(priv, address, amount, price: gas_price)
252
233
  raise 'Private key can\'t be nil' unless priv
253
234
  raise 'Private key must be a String' unless priv.is_a?(String)
254
235
  raise 'Invalid format of private key' unless /^[0-9a-fA-F]{64}$/.match?(priv)
@@ -256,15 +237,11 @@ class ERC20::Wallet
256
237
  raise 'Address must be a String' unless address.is_a?(String)
257
238
  raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
258
239
  raise 'Amount can\'t be nil' unless amount
259
- raise 'Amount must be an Integer' unless amount.is_a?(Integer)
260
- raise 'Amount must be a positive Integer' unless amount.positive?
261
- if gas_limit
262
- raise 'Gas limit must be an Integer' unless gas_limit.is_a?(Integer)
263
- raise 'Gas limit must be a positive Integer' unless gas_limit.positive?
264
- end
265
- if gas_price
266
- raise 'Gas price must be an Integer' unless gas_price.is_a?(Integer)
267
- raise 'Gas price must be a positive Integer' unless gas_price.positive?
240
+ raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
241
+ raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
242
+ if price
243
+ raise 'Gas price must be an Integer' unless price.is_a?(Integer)
244
+ raise 'Gas price must be a positive Integer' unless price.positive?
268
245
  end
269
246
  key = Eth::Key.new(priv: priv)
270
247
  from = key.address.to_s
@@ -275,8 +252,8 @@ class ERC20::Wallet
275
252
  {
276
253
  chain_id: @chain,
277
254
  nonce:,
278
- gas_price: gas_price,
279
- gas_limit: gas_limit || gas_estimate(from, address),
255
+ gas_price: price,
256
+ gas_limit: 22_000,
280
257
  to: address,
281
258
  value: amount
282
259
  }
@@ -438,15 +415,6 @@ class ERC20::Wallet
438
415
  JSONRPC::Client.new(url, connection:)
439
416
  end
440
417
 
441
- # How much gas should be spent in order to send a transaction from one
442
- # public address to another public address, possible carrying some data
443
- # inside the transaction.
444
- def gas_estimate(from, to, data = '')
445
- gas = jsonrpc.eth_estimateGas({ from:, to:, data: }, 'latest').to_i(16)
446
- @log.debug("Estimated gas is #{gas} ETH#{data.empty? ? '' : ', for ERC20 transfer'}")
447
- gas
448
- end
449
-
450
418
  def to_pay_data(address, amount)
451
419
  func = 'a9059cbb' # transfer(address,uint256)
452
420
  to_clean = address.downcase.sub(/^0x/, '')
@@ -455,8 +423,4 @@ class ERC20::Wallet
455
423
  amt_padded = ('0' * (64 - amt_hex.size)) + amt_hex
456
424
  "0x#{func}#{to_padded}#{amt_padded}"
457
425
  end
458
-
459
- def gas_best_price
460
- jsonrpc.eth_getBlockByNumber('latest', false)['baseFeePerGas'].to_i(16)
461
- end
462
426
  end
data/lib/erc20.rb CHANGED
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2025 Yegor Bugayenko
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the 'Software'), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- # SOFTWARE.
3
+ # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
22
5
 
23
6
  require_relative 'erc20/erc20'
24
7
  require_relative 'erc20/wallet'
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2025 Yegor Bugayenko
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the 'Software'), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- # SOFTWARE.
3
+ # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
22
5
 
23
6
  require 'backtrace'
24
7
  require 'donce'
@@ -38,14 +21,18 @@ require_relative '../test__helper'
38
21
  # Copyright:: Copyright (c) 2025 Yegor Bugayenko
39
22
  # License:: MIT
40
23
  class TestFakeWallet < Minitest::Test
41
- def test_checks_gas_required
42
- b = ERC20::FakeWallet.new.gas_required('0xEB2fE8872A6f1eDb70a2632Effffffffffffffff')
24
+ def test_checks_gas_estimate
25
+ b = ERC20::FakeWallet.new.gas_estimate(
26
+ '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff',
27
+ '0xfadef8ba4a5d709a2bf55b7a8798c9b438c640c1',
28
+ 44_000
29
+ )
43
30
  refute_nil(b)
44
31
  end
45
32
 
46
- def test_checks_eth_gas_required
47
- b = ERC20::FakeWallet.new.eth_gas_required('0xEB2fE8872A6f1eDb70a2632Effffffffffffffff')
48
- refute_nil(b)
33
+ def test_checks_gas_price
34
+ gwei = ERC20::FakeWallet.new.gas_price
35
+ refute_nil(gwei)
49
36
  end
50
37
 
51
38
  def test_checks_fake_balance
@@ -53,17 +40,35 @@ class TestFakeWallet < Minitest::Test
53
40
  a = '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff'
54
41
  b = w.balance(a)
55
42
  refute_nil(b)
43
+ assert_equal(42_000_000, b)
56
44
  assert_includes(w.history, { method: :balance, result: b, address: a })
57
45
  end
58
46
 
47
+ def test_checks_preset_fake_balance
48
+ w = ERC20::FakeWallet.new
49
+ a = '0xEB2fE8872A6f1eDb70a2632Effffffffeefbbfaa'
50
+ b = 55_555
51
+ w.set_balance(a, b)
52
+ assert_equal(b, w.balance(a))
53
+ end
54
+
59
55
  def test_checks_fake_eth_balance
60
- a = '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff'
56
+ a = '0xEB2fE8872A6f1eDb70a2632Ebbffffff66fff77f'
61
57
  w = ERC20::FakeWallet.new
62
58
  b = w.eth_balance(a)
63
59
  refute_nil(b)
60
+ assert_equal(77_000_000_000_000_000, b)
64
61
  assert_includes(w.history, { method: :eth_balance, result: b, address: a })
65
62
  end
66
63
 
64
+ def test_checks_preset_fake_eth_balance
65
+ a = '0xEB2fE8872A6f1eDb70a2632Eff88fff99fff33ff'
66
+ w = ERC20::FakeWallet.new
67
+ b = 33_333
68
+ w.set_eth_balance(a, b)
69
+ assert_equal(b, w.eth_balance(a))
70
+ end
71
+
67
72
  def test_returns_host
68
73
  assert_equal('example.com', ERC20::FakeWallet.new.host)
69
74
  end
@@ -76,6 +81,7 @@ class TestFakeWallet < Minitest::Test
76
81
  txn = w.pay(priv, address, amount)
77
82
  assert_equal(66, txn.length)
78
83
  assert_match(/^0x[a-f0-9]{64}$/, txn)
84
+ assert_equal(ERC20::FakeWallet::TXN_HASH, txn)
79
85
  assert_includes(w.history, { method: :pay, result: txn, priv:, address:, amount:, gas_limit: nil, gas_price: nil })
80
86
  end
81
87