erc20 0.0.19 → 0.0.20
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/erc20/erc20.rb +1 -1
- data/lib/erc20/fake_wallet.rb +15 -17
- data/lib/erc20/wallet.rb +41 -60
- data/test/erc20/test_fake_wallet.rb +9 -5
- data/test/erc20/test_wallet.rb +8 -27
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c6a14d29c4065075aa4c50b8101930a95b3ed09eff93ef8e37f145da576a27e
|
4
|
+
data.tar.gz: cd4ab161e890ba8cd4fdf4d60782c3d1550cadae6b6c74f4d2cec2967e3cda04
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a73ba1ba539a7d25fe7cf2ecb022ddf5d7fbdcd61cf1653d987b9cf29b5337229a2756853bc538f2ae7385bf141945cea50a5198214e8da8d29088a34dea63c9
|
7
|
+
data.tar.gz: 318f3270b986d11fb901af11181e457f89a53bc7ae89898a4c3231659edc1537933637285d51c9bac046f5be15b723d9709989def4532d0013ae872e50dadabd
|
data/lib/erc20/erc20.rb
CHANGED
data/lib/erc20/fake_wallet.rb
CHANGED
@@ -67,26 +67,24 @@ class ERC20::FakeWallet
|
|
67
67
|
b
|
68
68
|
end
|
69
69
|
|
70
|
-
# How much
|
70
|
+
# How much gas units is required in order to send ERC20 transaction.
|
71
71
|
#
|
72
72
|
# @param [String] from The departing address, in hex
|
73
73
|
# @param [String] to Arriving address, in hex
|
74
|
-
# @
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
74
|
+
# @param [Integer] amount How many ERC20 tokens to send
|
75
|
+
# @return [Integer] How many gas units required
|
76
|
+
def gas_estimate(from, to, amount)
|
77
|
+
gas = 66_000
|
78
|
+
@history << { method: :gas_estimate, from:, to:, amount:, result: gas }
|
79
|
+
gas
|
79
80
|
end
|
80
81
|
|
81
|
-
#
|
82
|
-
#
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
g = 55_000
|
88
|
-
@history << { method: :eth_gas_required, from:, to:, result: g }
|
89
|
-
g
|
82
|
+
# What is the price of gas unit in gwei?
|
83
|
+
# @return [Integer] Price of gas unit, in gwei (0.000000001 ETH)
|
84
|
+
def gas_price
|
85
|
+
gwei = 55_555
|
86
|
+
@history << { method: :gas_price, result: gwei }
|
87
|
+
gwei
|
90
88
|
end
|
91
89
|
|
92
90
|
# Send a single ERC20 payment from a private address to a public one.
|
@@ -107,9 +105,9 @@ class ERC20::FakeWallet
|
|
107
105
|
# @param [String] address Public key, in hex
|
108
106
|
# @param [Integer] amount The amount of ETHs to send
|
109
107
|
# @return [String] Transaction hash
|
110
|
-
def eth_pay(priv, address, amount,
|
108
|
+
def eth_pay(priv, address, amount, gas_price: nil)
|
111
109
|
hex = '0x172de9cda30537eae68ab4a96163ebbb8f8a85293b8737dd2e5deb4714b14623'
|
112
|
-
@history << { method: :eth_pay, priv:, address:, amount:,
|
110
|
+
@history << { method: :eth_pay, priv:, address:, amount:, gas_price:, result: hex }
|
113
111
|
hex
|
114
112
|
end
|
115
113
|
|
data/lib/erc20/wallet.rb
CHANGED
@@ -159,34 +159,33 @@ class ERC20::Wallet
|
|
159
159
|
b
|
160
160
|
end
|
161
161
|
|
162
|
-
# How much
|
162
|
+
# How much gas units is required in order to send ERC20 transaction.
|
163
163
|
#
|
164
164
|
# @param [String] from The departing address, in hex
|
165
|
-
# @param [String] to Arriving address, in hex
|
166
|
-
# @
|
167
|
-
|
165
|
+
# @param [String] to Arriving address, in hex
|
166
|
+
# @param [Integer] amount How many ERC20 tokens to send
|
167
|
+
# @return [Integer] How many gas units required
|
168
|
+
def gas_estimate(from, to, amount)
|
168
169
|
raise 'Address can\'t be nil' unless from
|
169
170
|
raise 'Address must be a String' unless from.is_a?(String)
|
170
171
|
raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(from)
|
171
172
|
raise 'Address can\'t be nil' unless to
|
172
173
|
raise 'Address must be a String' unless to.is_a?(String)
|
173
174
|
raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(to)
|
174
|
-
|
175
|
+
raise 'Amount can\'t be nil' unless amount
|
176
|
+
raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
|
177
|
+
raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
|
178
|
+
gas = jsonrpc.eth_estimateGas({ from:, to: @contract, data: to_pay_data(to, amount) }, 'latest').to_i(16)
|
179
|
+
@log.debug("It would take #{gas} gas units to send #{amount} tokens from #{from} to #{to}")
|
180
|
+
gas
|
175
181
|
end
|
176
182
|
|
177
|
-
#
|
178
|
-
#
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
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)
|
183
|
+
# What is the price of gas unit in gwei?
|
184
|
+
# @return [Integer] Price of gas unit, in gwei (0.000000001 ETH)
|
185
|
+
def gas_price
|
186
|
+
gwei = jsonrpc.eth_getBlockByNumber('latest', false)['baseFeePerGas'].to_i(16)
|
187
|
+
@log.debug("The cost of one gas unit is #{gwei} gwei")
|
188
|
+
gwei
|
190
189
|
end
|
191
190
|
|
192
191
|
# Send a single ERC20 payment from a private address to a public one.
|
@@ -194,10 +193,10 @@ class ERC20::Wallet
|
|
194
193
|
# @param [String] priv Private key, in hex
|
195
194
|
# @param [String] address Public key, in hex
|
196
195
|
# @param [Integer] amount The amount of ERC20 tokens to send
|
197
|
-
# @param [Integer]
|
198
|
-
# @param [Integer]
|
196
|
+
# @param [Integer] limit How much gas you're ready to spend
|
197
|
+
# @param [Integer] price How much gas you pay per computation unit
|
199
198
|
# @return [String] Transaction hash
|
200
|
-
def pay(priv, address, amount,
|
199
|
+
def pay(priv, address, amount, limit: nil, price: gas_price)
|
201
200
|
raise 'Private key can\'t be nil' unless priv
|
202
201
|
raise 'Private key must be a String' unless priv.is_a?(String)
|
203
202
|
raise 'Invalid format of private key' unless /^[0-9a-fA-F]{64}$/.match?(priv)
|
@@ -205,30 +204,29 @@ class ERC20::Wallet
|
|
205
204
|
raise 'Address must be a String' unless address.is_a?(String)
|
206
205
|
raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
|
207
206
|
raise 'Amount can\'t be nil' unless amount
|
208
|
-
raise
|
209
|
-
raise
|
210
|
-
if
|
211
|
-
raise 'Gas limit must be an Integer' unless
|
212
|
-
raise 'Gas limit must be a positive Integer' unless
|
207
|
+
raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
|
208
|
+
raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
|
209
|
+
if limit
|
210
|
+
raise 'Gas limit must be an Integer' unless limit.is_a?(Integer)
|
211
|
+
raise 'Gas limit must be a positive Integer' unless limit.positive?
|
213
212
|
end
|
214
|
-
if
|
215
|
-
raise 'Gas price must be an Integer' unless
|
216
|
-
raise 'Gas price must be a positive Integer' unless
|
213
|
+
if price
|
214
|
+
raise 'Gas price must be an Integer' unless price.is_a?(Integer)
|
215
|
+
raise 'Gas price must be a positive Integer' unless price.positive?
|
217
216
|
end
|
218
217
|
key = Eth::Key.new(priv: priv)
|
219
218
|
from = key.address.to_s
|
220
|
-
data = to_pay_data(address, amount)
|
221
219
|
tnx =
|
222
220
|
@mutex.synchronize do
|
223
221
|
nonce = jsonrpc.eth_getTransactionCount(from, 'pending').to_i(16)
|
224
222
|
tx = Eth::Tx.new(
|
225
223
|
{
|
226
224
|
nonce:,
|
227
|
-
gas_price:
|
228
|
-
gas_limit:
|
225
|
+
gas_price: price,
|
226
|
+
gas_limit: limit || gas_estimate(from, address, amount),
|
229
227
|
to: @contract,
|
230
228
|
value: 0,
|
231
|
-
data
|
229
|
+
data: to_pay_data(address, amount),
|
232
230
|
chain_id: @chain
|
233
231
|
}
|
234
232
|
)
|
@@ -245,10 +243,10 @@ class ERC20::Wallet
|
|
245
243
|
# @param [String] priv Private key, in hex
|
246
244
|
# @param [String] address Public key, in hex
|
247
245
|
# @param [Integer] amount The amount of ERC20 tokens to send
|
248
|
-
# @param [Integer]
|
249
|
-
# @param [Integer]
|
246
|
+
# @param [Integer] limit How much gas you're ready to spend
|
247
|
+
# @param [Integer] price How much gas you pay per computation unit
|
250
248
|
# @return [String] Transaction hash
|
251
|
-
def eth_pay(priv, address, amount,
|
249
|
+
def eth_pay(priv, address, amount, price: gas_price)
|
252
250
|
raise 'Private key can\'t be nil' unless priv
|
253
251
|
raise 'Private key must be a String' unless priv.is_a?(String)
|
254
252
|
raise 'Invalid format of private key' unless /^[0-9a-fA-F]{64}$/.match?(priv)
|
@@ -256,15 +254,11 @@ class ERC20::Wallet
|
|
256
254
|
raise 'Address must be a String' unless address.is_a?(String)
|
257
255
|
raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
|
258
256
|
raise 'Amount can\'t be nil' unless amount
|
259
|
-
raise
|
260
|
-
raise
|
261
|
-
if
|
262
|
-
raise 'Gas
|
263
|
-
raise 'Gas
|
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?
|
257
|
+
raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
|
258
|
+
raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
|
259
|
+
if price
|
260
|
+
raise 'Gas price must be an Integer' unless price.is_a?(Integer)
|
261
|
+
raise 'Gas price must be a positive Integer' unless price.positive?
|
268
262
|
end
|
269
263
|
key = Eth::Key.new(priv: priv)
|
270
264
|
from = key.address.to_s
|
@@ -275,8 +269,8 @@ class ERC20::Wallet
|
|
275
269
|
{
|
276
270
|
chain_id: @chain,
|
277
271
|
nonce:,
|
278
|
-
gas_price:
|
279
|
-
gas_limit:
|
272
|
+
gas_price: price,
|
273
|
+
gas_limit: 22_000,
|
280
274
|
to: address,
|
281
275
|
value: amount
|
282
276
|
}
|
@@ -438,15 +432,6 @@ class ERC20::Wallet
|
|
438
432
|
JSONRPC::Client.new(url, connection:)
|
439
433
|
end
|
440
434
|
|
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
435
|
def to_pay_data(address, amount)
|
451
436
|
func = 'a9059cbb' # transfer(address,uint256)
|
452
437
|
to_clean = address.downcase.sub(/^0x/, '')
|
@@ -455,8 +440,4 @@ class ERC20::Wallet
|
|
455
440
|
amt_padded = ('0' * (64 - amt_hex.size)) + amt_hex
|
456
441
|
"0x#{func}#{to_padded}#{amt_padded}"
|
457
442
|
end
|
458
|
-
|
459
|
-
def gas_best_price
|
460
|
-
jsonrpc.eth_getBlockByNumber('latest', false)['baseFeePerGas'].to_i(16)
|
461
|
-
end
|
462
443
|
end
|
@@ -38,14 +38,18 @@ require_relative '../test__helper'
|
|
38
38
|
# Copyright:: Copyright (c) 2025 Yegor Bugayenko
|
39
39
|
# License:: MIT
|
40
40
|
class TestFakeWallet < Minitest::Test
|
41
|
-
def
|
42
|
-
b = ERC20::FakeWallet.new.
|
41
|
+
def test_checks_gas_estimate
|
42
|
+
b = ERC20::FakeWallet.new.gas_estimate(
|
43
|
+
'0xEB2fE8872A6f1eDb70a2632Effffffffffffffff',
|
44
|
+
'0xfadef8ba4a5d709a2bf55b7a8798c9b438c640c1',
|
45
|
+
44_000
|
46
|
+
)
|
43
47
|
refute_nil(b)
|
44
48
|
end
|
45
49
|
|
46
|
-
def
|
47
|
-
|
48
|
-
refute_nil(
|
50
|
+
def test_checks_gas_price
|
51
|
+
gwei = ERC20::FakeWallet.new.gas_price
|
52
|
+
refute_nil(gwei)
|
49
53
|
end
|
50
54
|
|
51
55
|
def test_checks_fake_balance
|
data/test/erc20/test_wallet.rb
CHANGED
@@ -67,19 +67,13 @@ class TestWallet < Minitest::Test
|
|
67
67
|
assert_equal(0, b)
|
68
68
|
end
|
69
69
|
|
70
|
-
def
|
71
|
-
b = mainnet.
|
70
|
+
def test_checks_gas_estimate_on_mainnet
|
71
|
+
b = mainnet.gas_estimate(STABLE, Eth::Key.new(priv: JEFF).address.to_s, 44_000)
|
72
72
|
refute_nil(b)
|
73
73
|
assert_predicate(b, :positive?)
|
74
74
|
assert_operator(b, :>, 1000)
|
75
75
|
end
|
76
76
|
|
77
|
-
def test_checks_same_address_gas_required_on_mainnet
|
78
|
-
b = mainnet.gas_required(STABLE, STABLE)
|
79
|
-
refute_nil(b)
|
80
|
-
assert_predicate(b, :positive?)
|
81
|
-
end
|
82
|
-
|
83
77
|
def test_fails_with_invalid_infura_key
|
84
78
|
skip('Apparently, even with invalid key, Infura returns balance')
|
85
79
|
w = ERC20::Wallet.new(
|
@@ -107,28 +101,15 @@ class TestWallet < Minitest::Test
|
|
107
101
|
assert_predicate(b, :zero?)
|
108
102
|
end
|
109
103
|
|
110
|
-
def
|
111
|
-
|
112
|
-
b1 = wallet.gas_required(
|
113
|
-
Eth::Key.new(priv: JEFF).address.to_s,
|
114
|
-
Eth::Key.new(priv: WALTER).address.to_s
|
115
|
-
)
|
116
|
-
assert_equal(21_597, b1)
|
117
|
-
b2 = wallet.gas_required(
|
118
|
-
Eth::Key.new(priv: JEFF).address.to_s,
|
119
|
-
Eth::Key.new(priv: JEFF).address.to_s
|
120
|
-
)
|
121
|
-
assert_equal(b1, b2)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def test_checks_eth_gas_required_on_hardhat
|
104
|
+
def test_checks_gas_estimate_on_hardhat
|
105
|
+
sum = 100_000
|
126
106
|
on_hardhat do |wallet|
|
127
|
-
|
107
|
+
b1 = wallet.gas_estimate(
|
128
108
|
Eth::Key.new(priv: JEFF).address.to_s,
|
129
|
-
Eth::Key.new(priv: WALTER).address.to_s
|
109
|
+
Eth::Key.new(priv: WALTER).address.to_s,
|
110
|
+
sum
|
130
111
|
)
|
131
|
-
|
112
|
+
assert_operator(b1, :>, 21_000)
|
132
113
|
end
|
133
114
|
end
|
134
115
|
|