erc20 0.0.19 → 0.0.20
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 +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
|
|