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 +4 -4
- data/lib/erc20/erc20.rb +1 -1
- data/lib/erc20/fake_wallet.rb +18 -0
- data/lib/erc20/wallet.rb +39 -16
- data/test/erc20/test_fake_wallet.rb +16 -0
- data/test/erc20/test_wallet.rb +29 -3
- 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: 53d1ed4b12b1b7e295242f5345c3ebd4f22e8fe83af31bd2bcb4f27ec8a0b4ca
|
4
|
+
data.tar.gz: e377b3185614081e92f72a7fda5c9a8019de72a3497b9d28259e13f17a5f1ab0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 892709a9568e7837a708feefd8d7880d309fae9f921d1a8266e1b7b995acb1575a6305af92a1dd5dab7b9e7a7f3da9cc5b0b2539a9984eac91d291f59c5d86d1
|
7
|
+
data.tar.gz: d945d2ec483e4cc14ff76683294941cfaf9603963baf8c3acbf64189ee59aead1ec70e6b2d292c889d790c8f8f6fcd59c5327bb2a5223a138821fd5592971178
|
data/lib/erc20/erc20.rb
CHANGED
data/lib/erc20/fake_wallet.rb
CHANGED
@@ -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:
|
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
|
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
|
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:
|
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
|
255
|
-
gas_limit: gas_limit || gas_estimate(from, address
|
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
|
-
|
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)
|
data/test/erc20/test_wallet.rb
CHANGED
@@ -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(
|
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)
|