erc20 0.1.5 → 0.1.6
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/Gemfile +2 -2
- data/Gemfile.lock +6 -3
- data/lib/erc20/erc20.rb +1 -1
- data/lib/erc20/wallet.rb +32 -30
- data/test/erc20/test_wallet.rb +2 -228
- data/test/erc20/test_wallet_live.rb +126 -0
- data/test/test__helper.rb +126 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7843a4cb824e3689fb7a457f8ca0744f1d1059349aa6ea8bfe8805cd597e8b24
|
4
|
+
data.tar.gz: d11d0eb794c22737519ef5ee2083763ba8244ce91f01f0f120ee8e6970af53c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 135c421a05cbc431e20113e8cd6d63294630588b6ba27a63249313455853b83266d697759764b6f82aae857131237fd5c8c85f4f1432fe74cb73a25935b5ca45
|
7
|
+
data.tar.gz: ecf62b97b0053bc123de22f600e61a90cbf2e0636c1afa199d96478df0186194a917d27f0234285deb14129715970e2b818be5f6cf7d3a692d131a685a1af41f
|
data/Gemfile
CHANGED
@@ -9,13 +9,12 @@ gemspec
|
|
9
9
|
gem 'backtrace', '>0', require: false
|
10
10
|
gem 'concurrent-ruby', '~>1.2', require: false
|
11
11
|
gem 'cucumber', '~>9.2', require: false
|
12
|
-
gem 'donce', '
|
12
|
+
gem 'donce', '>0', require: false
|
13
13
|
gem 'faraday', '>0', require: false
|
14
14
|
gem 'loog', '>0', require: false
|
15
15
|
gem 'minitest', '~>5.25', require: false
|
16
16
|
gem 'minitest-reporters', '~>1.7', require: false
|
17
17
|
gem 'minitest-retry', '~>0.2', require: false
|
18
|
-
gem 'os', '>0', require: false
|
19
18
|
gem 'qbash', '>0', require: false
|
20
19
|
gem 'rake', '~>13.2', require: false
|
21
20
|
gem 'random-port', '>0', require: false
|
@@ -23,6 +22,7 @@ gem 'rubocop', '~>1.75', require: false
|
|
23
22
|
gem 'rubocop-minitest', '>0', require: false
|
24
23
|
gem 'rubocop-performance', '>0', require: false
|
25
24
|
gem 'rubocop-rake', '>0', require: false
|
25
|
+
gem 'rubocop-rspec', '>0', require: false
|
26
26
|
gem 'simplecov', '~>0.22', require: false
|
27
27
|
gem 'simplecov-cobertura', '~>2.1', require: false
|
28
28
|
gem 'threads', '~>0.4', require: false
|
data/Gemfile.lock
CHANGED
@@ -51,7 +51,7 @@ GEM
|
|
51
51
|
cucumber-tag-expressions (6.1.2)
|
52
52
|
diff-lcs (1.6.1)
|
53
53
|
docile (1.4.1)
|
54
|
-
donce (0.2.
|
54
|
+
donce (0.2.4)
|
55
55
|
backtrace (~> 0.3)
|
56
56
|
os (~> 1.1)
|
57
57
|
qbash (~> 0.3)
|
@@ -161,6 +161,9 @@ GEM
|
|
161
161
|
rubocop-rake (0.7.1)
|
162
162
|
lint_roller (~> 1.1)
|
163
163
|
rubocop (>= 1.72.1)
|
164
|
+
rubocop-rspec (3.6.0)
|
165
|
+
lint_roller (~> 1.1)
|
166
|
+
rubocop (~> 1.72, >= 1.72.1)
|
164
167
|
ruby-progressbar (1.13.0)
|
165
168
|
rubyzip (2.4.1)
|
166
169
|
scrypt (3.0.8)
|
@@ -211,14 +214,13 @@ DEPENDENCIES
|
|
211
214
|
backtrace (> 0)
|
212
215
|
concurrent-ruby (~> 1.2)
|
213
216
|
cucumber (~> 9.2)
|
214
|
-
donce (
|
217
|
+
donce (> 0)
|
215
218
|
erc20!
|
216
219
|
faraday (> 0)
|
217
220
|
loog (> 0)
|
218
221
|
minitest (~> 5.25)
|
219
222
|
minitest-reporters (~> 1.7)
|
220
223
|
minitest-retry (~> 0.2)
|
221
|
-
os (> 0)
|
222
224
|
qbash (> 0)
|
223
225
|
rake (~> 13.2)
|
224
226
|
random-port (> 0)
|
@@ -226,6 +228,7 @@ DEPENDENCIES
|
|
226
228
|
rubocop-minitest (> 0)
|
227
229
|
rubocop-performance (> 0)
|
228
230
|
rubocop-rake (> 0)
|
231
|
+
rubocop-rspec (> 0)
|
229
232
|
simplecov (~> 0.22)
|
230
233
|
simplecov-cobertura (~> 2.1)
|
231
234
|
threads (~> 0.4)
|
data/lib/erc20/erc20.rb
CHANGED
data/lib/erc20/wallet.rb
CHANGED
@@ -312,9 +312,11 @@ class ERC20::Wallet
|
|
312
312
|
raise 'Addresses can\'t be nil' unless addresses
|
313
313
|
raise 'Addresses must respond to .to_a()' unless addresses.respond_to?(:to_a)
|
314
314
|
raise 'Active can\'t be nil' unless active
|
315
|
+
raise 'Active must respond to .to_a()' unless active.respond_to?(:to_a)
|
315
316
|
raise 'Active must respond to .append()' unless active.respond_to?(:append)
|
317
|
+
raise 'Active must respond to .clear()' unless active.respond_to?(:clear)
|
316
318
|
raise 'Delay must be an Integer' unless delay.is_a?(Integer)
|
317
|
-
raise 'Delay must be a positive Integer' unless delay.positive?
|
319
|
+
raise 'Delay must be a positive Integer or positive Float' unless delay.positive?
|
318
320
|
raise 'Subscription ID must be an Integer' unless subscription_id.is_a?(Integer)
|
319
321
|
raise 'Subscription ID must be a positive Integer' unless subscription_id.positive?
|
320
322
|
EventMachine.run do
|
@@ -336,37 +338,38 @@ class ERC20::Wallet
|
|
336
338
|
contract = @contract
|
337
339
|
log_url = "ws#{@ssl ? 's' : ''}://#{u.hostname}:#{u.port}"
|
338
340
|
ws = Faye::WebSocket::Client.new(u.to_s, [], proxy: @proxy ? { origin: @proxy } : {}, ping: 60)
|
339
|
-
timer =
|
340
|
-
EventMachine.add_periodic_timer(delay) do
|
341
|
-
next if active.to_a.sort == addresses.to_a.sort
|
342
|
-
ws.send(
|
343
|
-
{
|
344
|
-
jsonrpc: '2.0',
|
345
|
-
id: subscription_id,
|
346
|
-
method: 'eth_subscribe',
|
347
|
-
params: [
|
348
|
-
'logs',
|
349
|
-
{
|
350
|
-
address: contract,
|
351
|
-
topics: [
|
352
|
-
'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
|
353
|
-
nil,
|
354
|
-
addresses.to_a.map { |a| "0x000000000000000000000000#{a[2..]}" }
|
355
|
-
]
|
356
|
-
}
|
357
|
-
]
|
358
|
-
}.to_json
|
359
|
-
)
|
360
|
-
log_it(
|
361
|
-
:debug,
|
362
|
-
"Requested to subscribe ##{subscription_id} to #{addresses.to_a.size} addresses: " \
|
363
|
-
"#{addresses.to_a.map { |a| a[0..6] }.join(', ')}"
|
364
|
-
)
|
365
|
-
end
|
341
|
+
timer = nil
|
366
342
|
ws.on(:open) do
|
367
343
|
safe do
|
368
344
|
verbose do
|
369
345
|
log_it(:debug, "Connected ##{subscription_id} to #{log_url}")
|
346
|
+
timer =
|
347
|
+
EventMachine.add_periodic_timer(delay) do
|
348
|
+
next if active.to_a.sort == addresses.to_a.sort
|
349
|
+
ws.send(
|
350
|
+
{
|
351
|
+
jsonrpc: '2.0',
|
352
|
+
id: subscription_id,
|
353
|
+
method: 'eth_subscribe',
|
354
|
+
params: [
|
355
|
+
'logs',
|
356
|
+
{
|
357
|
+
address: contract,
|
358
|
+
topics: [
|
359
|
+
'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
|
360
|
+
nil,
|
361
|
+
addresses.to_a.map { |a| "0x000000000000000000000000#{a[2..]}" }
|
362
|
+
]
|
363
|
+
}
|
364
|
+
]
|
365
|
+
}.to_json
|
366
|
+
)
|
367
|
+
log_it(
|
368
|
+
:debug,
|
369
|
+
"Requested to subscribe ##{subscription_id} to #{addresses.to_a.size} addresses: " \
|
370
|
+
"#{addresses.to_a.map { |a| a[0..6] }.join(', ')}"
|
371
|
+
)
|
372
|
+
end
|
370
373
|
end
|
371
374
|
end
|
372
375
|
end
|
@@ -411,9 +414,8 @@ class ERC20::Wallet
|
|
411
414
|
safe do
|
412
415
|
verbose do
|
413
416
|
log_it(:debug, "Disconnected ##{subscription_id} from #{log_url}")
|
414
|
-
sleep(delay)
|
415
417
|
active.clear
|
416
|
-
timer
|
418
|
+
timer&.cancel
|
417
419
|
reaccept(addresses, active, raw:, delay:, subscription_id: subscription_id + 1, &)
|
418
420
|
end
|
419
421
|
end
|
data/test/erc20/test_wallet.rb
CHANGED
@@ -22,58 +22,12 @@ require_relative '../../lib/erc20/wallet'
|
|
22
22
|
# Copyright:: Copyright (c) 2025 Yegor Bugayenko
|
23
23
|
# License:: MIT
|
24
24
|
class TestWallet < ERC20::Test
|
25
|
-
# At this address, in Ethereum mainnet, there are $8 USDT and 0.0042 ETH. I won't
|
26
|
-
# move them anyway, that's why tests can use this address forever.
|
27
|
-
STABLE = '0x7232148927F8a580053792f44D4d59d40Fd00ABD'
|
28
|
-
|
29
25
|
# One guy private hex.
|
30
26
|
JEFF = '81a9b2114d53731ecc84b261ef6c0387dde34d5907fe7b441240cc21d61bf80a'
|
31
27
|
|
32
28
|
# Another guy private hex.
|
33
29
|
WALTER = '91f9111b1744d55361e632771a4e53839e9442a9fef45febc0a5c838c686a15b'
|
34
30
|
|
35
|
-
def test_checks_balance_on_mainnet
|
36
|
-
WebMock.enable_net_connect!
|
37
|
-
b = mainnet.balance(STABLE)
|
38
|
-
refute_nil(b)
|
39
|
-
assert_equal(8_000_000, b) # this is $8 USDT
|
40
|
-
end
|
41
|
-
|
42
|
-
def test_checks_eth_balance_on_mainnet
|
43
|
-
WebMock.enable_net_connect!
|
44
|
-
b = mainnet.eth_balance(STABLE)
|
45
|
-
refute_nil(b)
|
46
|
-
assert_equal(4_200_000_000_000_000, b) # this is 0.0042 ETH
|
47
|
-
end
|
48
|
-
|
49
|
-
def test_checks_balance_of_absent_address
|
50
|
-
WebMock.enable_net_connect!
|
51
|
-
a = '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff'
|
52
|
-
b = mainnet.balance(a)
|
53
|
-
refute_nil(b)
|
54
|
-
assert_equal(0, b)
|
55
|
-
end
|
56
|
-
|
57
|
-
def test_checks_gas_estimate_on_mainnet
|
58
|
-
WebMock.enable_net_connect!
|
59
|
-
b = mainnet.gas_estimate(STABLE, Eth::Key.new(priv: JEFF).address.to_s, 44_000)
|
60
|
-
refute_nil(b)
|
61
|
-
assert_predicate(b, :positive?)
|
62
|
-
assert_operator(b, :>, 1000)
|
63
|
-
end
|
64
|
-
|
65
|
-
def test_fails_with_invalid_infura_key
|
66
|
-
WebMock.enable_net_connect!
|
67
|
-
skip('Apparently, even with invalid key, Infura returns balance')
|
68
|
-
w = ERC20::Wallet.new(
|
69
|
-
contract: ERC20::Wallet.USDT,
|
70
|
-
host: 'mainnet.infura.io',
|
71
|
-
http_path: '/v3/invalid-key-here',
|
72
|
-
log: fake_loog
|
73
|
-
)
|
74
|
-
assert_raises(StandardError) { w.balance(STABLE) }
|
75
|
-
end
|
76
|
-
|
77
31
|
def test_logs_to_stdout
|
78
32
|
WebMock.disable_net_connect!
|
79
33
|
stub_request(:post, 'https://example.org/').to_return(
|
@@ -85,25 +39,12 @@ class TestWallet < ERC20::Test
|
|
85
39
|
http_path: '/',
|
86
40
|
log: $stdout
|
87
41
|
)
|
88
|
-
w.balance(
|
42
|
+
w.balance(Eth::Key.new(priv: JEFF).address.to_s)
|
89
43
|
end
|
90
44
|
|
91
45
|
def test_checks_balance_on_testnet
|
92
46
|
WebMock.enable_net_connect!
|
93
|
-
b = testnet.balance(
|
94
|
-
refute_nil(b)
|
95
|
-
assert_predicate(b, :zero?)
|
96
|
-
end
|
97
|
-
|
98
|
-
def test_checks_balance_on_polygon
|
99
|
-
WebMock.enable_net_connect!
|
100
|
-
w = ERC20::Wallet.new(
|
101
|
-
contract: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
|
102
|
-
host: 'polygon-mainnet.infura.io',
|
103
|
-
http_path: "/v3/#{env('INFURA_KEY')}",
|
104
|
-
log: fake_loog
|
105
|
-
)
|
106
|
-
b = w.balance(STABLE)
|
47
|
+
b = testnet.balance(Eth::Key.new(priv: JEFF).address.to_s)
|
107
48
|
refute_nil(b)
|
108
49
|
assert_predicate(b, :zero?)
|
109
50
|
end
|
@@ -379,26 +320,6 @@ class TestWallet < ERC20::Test
|
|
379
320
|
end
|
380
321
|
end
|
381
322
|
|
382
|
-
def test_accepts_payments_on_mainnet
|
383
|
-
WebMock.enable_net_connect!
|
384
|
-
active = []
|
385
|
-
failed = false
|
386
|
-
net = mainnet
|
387
|
-
daemon =
|
388
|
-
Thread.new do
|
389
|
-
net.accept([STABLE], active) do |_|
|
390
|
-
# ignore it
|
391
|
-
end
|
392
|
-
rescue StandardError => e
|
393
|
-
failed = true
|
394
|
-
fake_loog.error(Backtrace.new(e))
|
395
|
-
end
|
396
|
-
wait_for { !active.empty? }
|
397
|
-
daemon.kill
|
398
|
-
daemon.join(30)
|
399
|
-
refute(failed)
|
400
|
-
end
|
401
|
-
|
402
323
|
def test_checks_balance_via_proxy
|
403
324
|
WebMock.enable_net_connect!
|
404
325
|
b = nil
|
@@ -410,151 +331,4 @@ class TestWallet < ERC20::Test
|
|
410
331
|
end
|
411
332
|
assert_equal(123_000_100_000, b)
|
412
333
|
end
|
413
|
-
|
414
|
-
def test_checks_balance_via_proxy_on_mainnet
|
415
|
-
WebMock.enable_net_connect!
|
416
|
-
via_proxy do |proxy|
|
417
|
-
w = ERC20::Wallet.new(
|
418
|
-
host: 'mainnet.infura.io',
|
419
|
-
http_path: "/v3/#{env('INFURA_KEY')}",
|
420
|
-
proxy:, log: fake_loog
|
421
|
-
)
|
422
|
-
assert_equal(8_000_000, w.balance(STABLE))
|
423
|
-
end
|
424
|
-
end
|
425
|
-
|
426
|
-
def test_pays_on_mainnet
|
427
|
-
WebMock.enable_net_connect!
|
428
|
-
skip('This is live, must be run manually')
|
429
|
-
w = mainnet
|
430
|
-
print 'Enter Ethereum ERC20 private key (64 chars): '
|
431
|
-
priv = gets.chomp
|
432
|
-
to = '0xEB2fE8872A6f1eDb70a2632EA1f869AB131532f6'
|
433
|
-
txn = w.pay(priv, to, 1_990_000)
|
434
|
-
assert_equal(66, txn.length)
|
435
|
-
end
|
436
|
-
|
437
|
-
private
|
438
|
-
|
439
|
-
def env(var)
|
440
|
-
key = ENV.fetch(var, nil)
|
441
|
-
skip("The #{var} environment variable is not set") if key.nil?
|
442
|
-
skip("The #{var} environment variable is empty") if key.empty?
|
443
|
-
key
|
444
|
-
end
|
445
|
-
|
446
|
-
def mainnet
|
447
|
-
[
|
448
|
-
{
|
449
|
-
host: 'mainnet.infura.io',
|
450
|
-
http_path: "/v3/#{env('INFURA_KEY')}",
|
451
|
-
ws_path: "/ws/v3/#{env('INFURA_KEY')}"
|
452
|
-
},
|
453
|
-
{
|
454
|
-
host: 'go.getblock.io',
|
455
|
-
http_path: "/#{env('GETBLOCK_KEY')}",
|
456
|
-
ws_path: "/#{env('GETBLOCK_WS_KEY')}"
|
457
|
-
}
|
458
|
-
].map do |server|
|
459
|
-
ERC20::Wallet.new(
|
460
|
-
host: server[:host],
|
461
|
-
http_path: server[:http_path],
|
462
|
-
ws_path: server[:ws_path],
|
463
|
-
log: fake_loog
|
464
|
-
)
|
465
|
-
end.sample
|
466
|
-
end
|
467
|
-
|
468
|
-
def testnet
|
469
|
-
[
|
470
|
-
{
|
471
|
-
host: 'sepolia.infura.io',
|
472
|
-
http_path: "/v3/#{env('INFURA_KEY')}",
|
473
|
-
ws_path: "/ws/v3/#{env('INFURA_KEY')}"
|
474
|
-
},
|
475
|
-
{
|
476
|
-
host: 'go.getblock.io',
|
477
|
-
http_path: "/#{env('GETBLOCK_SEPOILA_KEY')}",
|
478
|
-
ws_path: "/#{env('GETBLOCK_SEPOILA_KEY')}"
|
479
|
-
}
|
480
|
-
].map do |server|
|
481
|
-
ERC20::Wallet.new(
|
482
|
-
host: server[:host],
|
483
|
-
http_path: server[:http_path],
|
484
|
-
ws_path: server[:ws_path],
|
485
|
-
log: fake_loog
|
486
|
-
)
|
487
|
-
end.sample
|
488
|
-
end
|
489
|
-
|
490
|
-
def through_proxy(wallet, proxy)
|
491
|
-
ERC20::Wallet.new(
|
492
|
-
contract: wallet.contract, chain: wallet.chain,
|
493
|
-
host: donce_host, port: wallet.port, http_path: wallet.http_path, ws_path: wallet.ws_path,
|
494
|
-
ssl: wallet.ssl, proxy:, log: fake_loog
|
495
|
-
)
|
496
|
-
end
|
497
|
-
|
498
|
-
def via_proxy
|
499
|
-
RandomPort::Pool::SINGLETON.acquire do |port|
|
500
|
-
donce(
|
501
|
-
image: 'yegor256/squid-proxy:latest',
|
502
|
-
ports: { port => 3128 },
|
503
|
-
env: { 'USERNAME' => 'jeffrey', 'PASSWORD' => 'swordfish' },
|
504
|
-
root: true, log: fake_loog
|
505
|
-
) do
|
506
|
-
yield "http://jeffrey:swordfish@localhost:#{port}"
|
507
|
-
end
|
508
|
-
end
|
509
|
-
end
|
510
|
-
|
511
|
-
def on_hardhat(port: nil, die: nil)
|
512
|
-
RandomPort::Pool::SINGLETON.acquire do |rnd|
|
513
|
-
port = rnd if port.nil?
|
514
|
-
if die
|
515
|
-
killer = [
|
516
|
-
'&',
|
517
|
-
'HARDHAT_PID=$!;',
|
518
|
-
'export HARDHAT_PID;',
|
519
|
-
'while true; do',
|
520
|
-
" if [ -e #{Shellwords.escape(File.join('/die', File.basename(die)))} ]; then",
|
521
|
-
' kill -9 "${HARDHAT_PID}";',
|
522
|
-
' break;',
|
523
|
-
' else',
|
524
|
-
' sleep 0.1;',
|
525
|
-
' fi;',
|
526
|
-
'done'
|
527
|
-
].join(' ')
|
528
|
-
end
|
529
|
-
cmd = "npx hardhat node #{killer if die}"
|
530
|
-
donce(
|
531
|
-
home: File.join(__dir__, '../../hardhat'),
|
532
|
-
ports: { port => 8545 },
|
533
|
-
volumes: die ? { File.dirname(die) => '/die' } : {},
|
534
|
-
command: "/bin/bash -c #{Shellwords.escape(cmd)}",
|
535
|
-
log: fake_loog
|
536
|
-
) do
|
537
|
-
wait_for_port(port)
|
538
|
-
cmd = [
|
539
|
-
'(cat hardhat.config.js)',
|
540
|
-
'(ls -al)',
|
541
|
-
'(echo y | npx hardhat ignition deploy ./ignition/modules/Foo.ts --network foo --deployment-id foo)',
|
542
|
-
'(npx hardhat ignition status foo | tail -1 | cut -d" " -f3)'
|
543
|
-
].join(' && ')
|
544
|
-
contract = donce(
|
545
|
-
home: File.join(__dir__, '../../hardhat'),
|
546
|
-
command: "/bin/bash -c #{Shellwords.escape(cmd)}",
|
547
|
-
build_args: { 'HOST' => donce_host, 'PORT' => port },
|
548
|
-
log: fake_loog,
|
549
|
-
root: true
|
550
|
-
).split("\n").last
|
551
|
-
wallet = ERC20::Wallet.new(
|
552
|
-
contract:, chain: 4242,
|
553
|
-
host: 'localhost', port:, http_path: '/', ws_path: '/', ssl: false,
|
554
|
-
log: fake_loog
|
555
|
-
)
|
556
|
-
yield wallet
|
557
|
-
end
|
558
|
-
end
|
559
|
-
end
|
560
334
|
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
|
6
|
+
require 'backtrace'
|
7
|
+
require 'donce'
|
8
|
+
require 'eth'
|
9
|
+
require 'faraday'
|
10
|
+
require 'fileutils'
|
11
|
+
require 'json'
|
12
|
+
require 'os'
|
13
|
+
require 'random-port'
|
14
|
+
require 'shellwords'
|
15
|
+
require 'threads'
|
16
|
+
require 'typhoeus'
|
17
|
+
require_relative '../test__helper'
|
18
|
+
require_relative '../../lib/erc20/wallet'
|
19
|
+
|
20
|
+
# Test.
|
21
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
22
|
+
# Copyright:: Copyright (c) 2025 Yegor Bugayenko
|
23
|
+
# License:: MIT
|
24
|
+
class TestWalletLive < ERC20::Test
|
25
|
+
# At this address, in Ethereum mainnet, there are $8 USDT and 0.0042 ETH. I won't
|
26
|
+
# move them anyway, that's why tests can use this address forever.
|
27
|
+
STABLE = '0x7232148927F8a580053792f44D4d59d40Fd00ABD'
|
28
|
+
|
29
|
+
def test_checks_balance_on_mainnet
|
30
|
+
WebMock.enable_net_connect!
|
31
|
+
b = mainnet.balance(STABLE)
|
32
|
+
refute_nil(b)
|
33
|
+
assert_equal(8_000_000, b) # this is $8 USDT
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_checks_eth_balance_on_mainnet
|
37
|
+
WebMock.enable_net_connect!
|
38
|
+
b = mainnet.eth_balance(STABLE)
|
39
|
+
refute_nil(b)
|
40
|
+
assert_equal(4_200_000_000_000_000, b) # this is 0.0042 ETH
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_checks_balance_of_absent_address
|
44
|
+
WebMock.enable_net_connect!
|
45
|
+
a = '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff'
|
46
|
+
b = mainnet.balance(a)
|
47
|
+
refute_nil(b)
|
48
|
+
assert_equal(0, b)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_checks_gas_estimate_on_mainnet
|
52
|
+
WebMock.enable_net_connect!
|
53
|
+
b = mainnet.gas_estimate(STABLE, '0x7232148927F8a580053792f44D4d5FFFFFFFFFFF', 44_000)
|
54
|
+
refute_nil(b)
|
55
|
+
assert_predicate(b, :positive?)
|
56
|
+
assert_operator(b, :>, 1000)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_fails_with_invalid_infura_key
|
60
|
+
WebMock.enable_net_connect!
|
61
|
+
skip('Apparently, even with invalid key, Infura returns balance')
|
62
|
+
w = ERC20::Wallet.new(
|
63
|
+
contract: ERC20::Wallet.USDT,
|
64
|
+
host: 'mainnet.infura.io',
|
65
|
+
http_path: '/v3/invalid-key-here',
|
66
|
+
log: fake_loog
|
67
|
+
)
|
68
|
+
assert_raises(StandardError) { w.balance(STABLE) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_checks_balance_on_polygon
|
72
|
+
WebMock.enable_net_connect!
|
73
|
+
w = ERC20::Wallet.new(
|
74
|
+
contract: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
|
75
|
+
host: 'polygon-mainnet.infura.io',
|
76
|
+
http_path: "/v3/#{env('INFURA_KEY')}",
|
77
|
+
log: fake_loog
|
78
|
+
)
|
79
|
+
b = w.balance(STABLE)
|
80
|
+
refute_nil(b)
|
81
|
+
assert_predicate(b, :zero?)
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_accepts_payments_on_mainnet
|
85
|
+
WebMock.enable_net_connect!
|
86
|
+
active = []
|
87
|
+
failed = false
|
88
|
+
net = mainnet
|
89
|
+
daemon =
|
90
|
+
Thread.new do
|
91
|
+
net.accept([STABLE], active) do |_|
|
92
|
+
# ignore it
|
93
|
+
end
|
94
|
+
rescue StandardError => e
|
95
|
+
failed = true
|
96
|
+
fake_loog.error(Backtrace.new(e))
|
97
|
+
end
|
98
|
+
wait_for { !active.empty? }
|
99
|
+
daemon.kill
|
100
|
+
daemon.join(30)
|
101
|
+
refute(failed)
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_checks_balance_via_proxy_on_mainnet
|
105
|
+
WebMock.enable_net_connect!
|
106
|
+
via_proxy do |proxy|
|
107
|
+
w = ERC20::Wallet.new(
|
108
|
+
host: 'mainnet.infura.io',
|
109
|
+
http_path: "/v3/#{env('INFURA_KEY')}",
|
110
|
+
proxy:, log: fake_loog
|
111
|
+
)
|
112
|
+
assert_equal(8_000_000, w.balance(STABLE))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_pays_on_mainnet
|
117
|
+
WebMock.enable_net_connect!
|
118
|
+
skip('This is live, must be run manually')
|
119
|
+
w = mainnet
|
120
|
+
print 'Enter Ethereum ERC20 private key (64 chars): '
|
121
|
+
priv = gets.chomp
|
122
|
+
to = '0xEB2fE8872A6f1eDb70a2632EA1f869AB131532f6'
|
123
|
+
txn = w.pay(priv, to, 1_990_000)
|
124
|
+
assert_equal(66, txn.length)
|
125
|
+
end
|
126
|
+
end
|
data/test/test__helper.rb
CHANGED
@@ -42,6 +42,10 @@ class Primitivo
|
|
42
42
|
@array = array
|
43
43
|
end
|
44
44
|
|
45
|
+
def clear
|
46
|
+
@array.clear
|
47
|
+
end
|
48
|
+
|
45
49
|
def to_a
|
46
50
|
@array.to_a
|
47
51
|
end
|
@@ -77,4 +81,126 @@ class ERC20::Test < Minitest::Test
|
|
77
81
|
def wait_for_port(port)
|
78
82
|
wait_for { Typhoeus::Request.get("http://localhost:#{port}").code == 200 }
|
79
83
|
end
|
84
|
+
|
85
|
+
def env(var)
|
86
|
+
key = ENV.fetch(var, nil)
|
87
|
+
skip("The #{var} environment variable is not set") if key.nil?
|
88
|
+
skip("The #{var} environment variable is empty") if key.empty?
|
89
|
+
key
|
90
|
+
end
|
91
|
+
|
92
|
+
def mainnet
|
93
|
+
[
|
94
|
+
{
|
95
|
+
host: 'mainnet.infura.io',
|
96
|
+
http_path: "/v3/#{env('INFURA_KEY')}",
|
97
|
+
ws_path: "/ws/v3/#{env('INFURA_KEY')}"
|
98
|
+
},
|
99
|
+
{
|
100
|
+
host: 'go.getblock.io',
|
101
|
+
http_path: "/#{env('GETBLOCK_KEY')}",
|
102
|
+
ws_path: "/#{env('GETBLOCK_WS_KEY')}"
|
103
|
+
}
|
104
|
+
].map do |server|
|
105
|
+
ERC20::Wallet.new(
|
106
|
+
host: server[:host],
|
107
|
+
http_path: server[:http_path],
|
108
|
+
ws_path: server[:ws_path],
|
109
|
+
log: fake_loog
|
110
|
+
)
|
111
|
+
end.sample
|
112
|
+
end
|
113
|
+
|
114
|
+
def testnet
|
115
|
+
[
|
116
|
+
{
|
117
|
+
host: 'sepolia.infura.io',
|
118
|
+
http_path: "/v3/#{env('INFURA_KEY')}",
|
119
|
+
ws_path: "/ws/v3/#{env('INFURA_KEY')}"
|
120
|
+
},
|
121
|
+
{
|
122
|
+
host: 'go.getblock.io',
|
123
|
+
http_path: "/#{env('GETBLOCK_SEPOILA_KEY')}",
|
124
|
+
ws_path: "/#{env('GETBLOCK_SEPOILA_KEY')}"
|
125
|
+
}
|
126
|
+
].map do |server|
|
127
|
+
ERC20::Wallet.new(
|
128
|
+
host: server[:host],
|
129
|
+
http_path: server[:http_path],
|
130
|
+
ws_path: server[:ws_path],
|
131
|
+
log: fake_loog
|
132
|
+
)
|
133
|
+
end.sample
|
134
|
+
end
|
135
|
+
|
136
|
+
def through_proxy(wallet, proxy)
|
137
|
+
ERC20::Wallet.new(
|
138
|
+
contract: wallet.contract, chain: wallet.chain,
|
139
|
+
host: donce_host, port: wallet.port, http_path: wallet.http_path, ws_path: wallet.ws_path,
|
140
|
+
ssl: wallet.ssl, proxy:, log: fake_loog
|
141
|
+
)
|
142
|
+
end
|
143
|
+
|
144
|
+
def via_proxy
|
145
|
+
RandomPort::Pool::SINGLETON.acquire do |port|
|
146
|
+
donce(
|
147
|
+
image: 'yegor256/squid-proxy:latest',
|
148
|
+
ports: { port => 3128 },
|
149
|
+
env: { 'USERNAME' => 'jeffrey', 'PASSWORD' => 'swordfish' },
|
150
|
+
root: true, log: fake_loog
|
151
|
+
) do
|
152
|
+
yield "http://jeffrey:swordfish@localhost:#{port}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def on_hardhat(port: nil, die: nil)
|
158
|
+
RandomPort::Pool::SINGLETON.acquire do |rnd|
|
159
|
+
port = rnd if port.nil?
|
160
|
+
if die
|
161
|
+
killer = [
|
162
|
+
'&',
|
163
|
+
'HARDHAT_PID=$!;',
|
164
|
+
'export HARDHAT_PID;',
|
165
|
+
'while true; do',
|
166
|
+
" if [ -e #{Shellwords.escape(File.join('/die', File.basename(die)))} ]; then",
|
167
|
+
' kill -9 "${HARDHAT_PID}";',
|
168
|
+
' break;',
|
169
|
+
' else',
|
170
|
+
' sleep 0.1;',
|
171
|
+
' fi;',
|
172
|
+
'done'
|
173
|
+
].join(' ')
|
174
|
+
end
|
175
|
+
cmd = "npx hardhat node #{killer if die}"
|
176
|
+
donce(
|
177
|
+
home: File.join(__dir__, '../hardhat'),
|
178
|
+
ports: { port => 8545 },
|
179
|
+
volumes: die ? { File.dirname(die) => '/die' } : {},
|
180
|
+
command: "/bin/bash -c #{Shellwords.escape(cmd)}",
|
181
|
+
log: fake_loog
|
182
|
+
) do
|
183
|
+
wait_for_port(port)
|
184
|
+
cmd = [
|
185
|
+
'(cat hardhat.config.js)',
|
186
|
+
'(ls -al)',
|
187
|
+
'(echo y | npx hardhat ignition deploy ./ignition/modules/Foo.ts --network foo --deployment-id foo)',
|
188
|
+
'(npx hardhat ignition status foo | tail -1 | cut -d" " -f3)'
|
189
|
+
].join(' && ')
|
190
|
+
contract = donce(
|
191
|
+
home: File.join(__dir__, '../hardhat'),
|
192
|
+
command: "/bin/bash -c #{Shellwords.escape(cmd)}",
|
193
|
+
build_args: { 'HOST' => donce_host, 'PORT' => port },
|
194
|
+
log: fake_loog,
|
195
|
+
root: true
|
196
|
+
).split("\n").last
|
197
|
+
wallet = ERC20::Wallet.new(
|
198
|
+
contract:, chain: 4242,
|
199
|
+
host: 'localhost', port:, http_path: '/', ws_path: '/', ssl: false,
|
200
|
+
log: fake_loog
|
201
|
+
)
|
202
|
+
yield wallet
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
80
206
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: erc20
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-04-
|
10
|
+
date: 2025-04-24 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: eth
|
@@ -149,6 +149,7 @@ files:
|
|
149
149
|
- renovate.json
|
150
150
|
- test/erc20/test_fake_wallet.rb
|
151
151
|
- test/erc20/test_wallet.rb
|
152
|
+
- test/erc20/test_wallet_live.rb
|
152
153
|
- test/test__helper.rb
|
153
154
|
homepage: http://github.com/yegor256/erc20.rb
|
154
155
|
licenses:
|