erc20 0.1.5 → 0.2.0
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/.github/workflows/hadolint.yml +16 -0
- data/.github/workflows/typos.yml +19 -0
- data/.gitignore +2 -2
- data/Gemfile +2 -2
- data/Gemfile.lock +17 -13
- data/REUSE.toml +11 -11
- data/hardhat/Dockerfile +5 -7
- data/lib/erc20/erc20.rb +1 -1
- data/lib/erc20/fake_wallet.rb +8 -0
- data/lib/erc20/wallet.rb +54 -30
- data/test/erc20/test_fake_wallet.rb +5 -0
- data/test/erc20/test_wallet.rb +11 -228
- data/test/erc20/test_wallet_live.rb +126 -0
- data/test/test__helper.rb +126 -0
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e76d55c6807ddcd06e895bc54cd2edd16ad079b48131eaac1b118005f1b1d5b
|
4
|
+
data.tar.gz: 2bd2c637aa1e05d82695eb6581fed32992aabb45ed47dec179b78879380d1869
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9e574760a4e257e5b8e5131b25a8af9cf5b7dcbb4e32e03093f50114cc84d308faa691032d155e6ebe291c4ce4ad838f0253e46942a8b146b324ef6fe72f88d
|
7
|
+
data.tar.gz: fad0c35c9e011414a49cc727d5ff3321b74a4c6424463d7f5c947c74c73d0f8e1e22b91702d5341fea0988c6c5b5ed31ee3e625675393cbf7165e4163f0aa70a
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
---
|
4
|
+
name: hadolint
|
5
|
+
'on':
|
6
|
+
push:
|
7
|
+
pull_request:
|
8
|
+
jobs:
|
9
|
+
hadolint:
|
10
|
+
timeout-minutes: 15
|
11
|
+
runs-on: ubuntu-24.04
|
12
|
+
steps:
|
13
|
+
- uses: actions/checkout@v4
|
14
|
+
- uses: hadolint/hadolint-action@v3.1.0
|
15
|
+
with:
|
16
|
+
dockerfile: hardhat/Dockerfile
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
---
|
4
|
+
# yamllint disable rule:line-length
|
5
|
+
name: typos
|
6
|
+
'on':
|
7
|
+
push:
|
8
|
+
branches:
|
9
|
+
- master
|
10
|
+
pull_request:
|
11
|
+
branches:
|
12
|
+
- master
|
13
|
+
jobs:
|
14
|
+
typos:
|
15
|
+
timeout-minutes: 15
|
16
|
+
runs-on: ubuntu-24.04
|
17
|
+
steps:
|
18
|
+
- uses: actions/checkout@v4
|
19
|
+
- uses: crate-ci/typos@v1.32.0
|
data/.gitignore
CHANGED
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
@@ -49,9 +49,9 @@ GEM
|
|
49
49
|
cucumber-messages (> 19, < 28)
|
50
50
|
cucumber-messages (22.0.0)
|
51
51
|
cucumber-tag-expressions (6.1.2)
|
52
|
-
diff-lcs (1.6.
|
52
|
+
diff-lcs (1.6.2)
|
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)
|
@@ -69,7 +69,7 @@ GEM
|
|
69
69
|
ethon (0.16.0)
|
70
70
|
ffi (>= 1.15.0)
|
71
71
|
eventmachine (1.2.7)
|
72
|
-
faraday (2.13.
|
72
|
+
faraday (2.13.1)
|
73
73
|
faraday-net_http (>= 2.0, < 3.5)
|
74
74
|
json
|
75
75
|
logger
|
@@ -87,18 +87,19 @@ GEM
|
|
87
87
|
rake
|
88
88
|
forwardable (1.3.3)
|
89
89
|
hashdiff (1.1.2)
|
90
|
-
json (2.
|
90
|
+
json (2.12.0)
|
91
91
|
jsonrpc-client (0.1.4)
|
92
92
|
faraday
|
93
93
|
multi_json (>= 1.1.0)
|
94
94
|
keccak (1.3.2)
|
95
95
|
konstructor (1.0.2)
|
96
|
-
language_server-protocol (3.17.0.
|
96
|
+
language_server-protocol (3.17.0.5)
|
97
97
|
lint_roller (1.1.0)
|
98
98
|
logger (1.7.0)
|
99
|
-
loog (0.6.
|
99
|
+
loog (0.6.1)
|
100
|
+
logger (~> 1.0)
|
100
101
|
mini_mime (1.1.5)
|
101
|
-
mini_portile2 (2.8.
|
102
|
+
mini_portile2 (2.8.9)
|
102
103
|
minitest (5.25.5)
|
103
104
|
minitest-reporters (1.7.1)
|
104
105
|
ansi
|
@@ -117,10 +118,10 @@ GEM
|
|
117
118
|
parser (3.3.8.0)
|
118
119
|
ast (~> 2.4.1)
|
119
120
|
racc
|
120
|
-
pkg-config (1.6.
|
121
|
+
pkg-config (1.6.2)
|
121
122
|
prism (1.4.0)
|
122
|
-
public_suffix (6.0.
|
123
|
-
qbash (0.4.
|
123
|
+
public_suffix (6.0.2)
|
124
|
+
qbash (0.4.5)
|
124
125
|
backtrace (> 0)
|
125
126
|
elapsed (> 0)
|
126
127
|
loog (> 0)
|
@@ -136,7 +137,7 @@ GEM
|
|
136
137
|
rubyzip (~> 2.3)
|
137
138
|
regexp_parser (2.10.0)
|
138
139
|
rexml (3.4.1)
|
139
|
-
rubocop (1.75.
|
140
|
+
rubocop (1.75.5)
|
140
141
|
json (~> 2.3)
|
141
142
|
language_server-protocol (~> 3.17.0.2)
|
142
143
|
lint_roller (~> 1.1.0)
|
@@ -161,6 +162,9 @@ GEM
|
|
161
162
|
rubocop-rake (0.7.1)
|
162
163
|
lint_roller (~> 1.1)
|
163
164
|
rubocop (>= 1.72.1)
|
165
|
+
rubocop-rspec (3.6.0)
|
166
|
+
lint_roller (~> 1.1)
|
167
|
+
rubocop (~> 1.72, >= 1.72.1)
|
164
168
|
ruby-progressbar (1.13.0)
|
165
169
|
rubyzip (2.4.1)
|
166
170
|
scrypt (3.0.8)
|
@@ -211,14 +215,13 @@ DEPENDENCIES
|
|
211
215
|
backtrace (> 0)
|
212
216
|
concurrent-ruby (~> 1.2)
|
213
217
|
cucumber (~> 9.2)
|
214
|
-
donce (
|
218
|
+
donce (> 0)
|
215
219
|
erc20!
|
216
220
|
faraday (> 0)
|
217
221
|
loog (> 0)
|
218
222
|
minitest (~> 5.25)
|
219
223
|
minitest-reporters (~> 1.7)
|
220
224
|
minitest-retry (~> 0.2)
|
221
|
-
os (> 0)
|
222
225
|
qbash (> 0)
|
223
226
|
rake (~> 13.2)
|
224
227
|
random-port (> 0)
|
@@ -226,6 +229,7 @@ DEPENDENCIES
|
|
226
229
|
rubocop-minitest (> 0)
|
227
230
|
rubocop-performance (> 0)
|
228
231
|
rubocop-rake (> 0)
|
232
|
+
rubocop-rspec (> 0)
|
229
233
|
simplecov (~> 0.22)
|
230
234
|
simplecov-cobertura (~> 2.1)
|
231
235
|
threads (~> 0.4)
|
data/REUSE.toml
CHANGED
@@ -4,10 +4,19 @@
|
|
4
4
|
version = 1
|
5
5
|
[[annotations]]
|
6
6
|
path = [
|
7
|
+
".DS_Store",
|
8
|
+
".gitattributes",
|
9
|
+
".gitignore",
|
10
|
+
".gitleaksignore",
|
11
|
+
".pdd",
|
7
12
|
"**.json",
|
8
13
|
"**.md",
|
9
14
|
"**.png",
|
10
15
|
"**.txt",
|
16
|
+
"**/.DS_Store",
|
17
|
+
"**/.gitignore",
|
18
|
+
"**/.gitleaksignore",
|
19
|
+
"**/.pdd",
|
11
20
|
"**/*.csv",
|
12
21
|
"**/*.jpg",
|
13
22
|
"**/*.json",
|
@@ -17,23 +26,14 @@ path = [
|
|
17
26
|
"**/*.svg",
|
18
27
|
"**/*.txt",
|
19
28
|
"**/*.vm",
|
20
|
-
"**/.DS_Store",
|
21
|
-
"**/.gitignore",
|
22
|
-
"**/.gitleaksignore",
|
23
|
-
"**/.pdd",
|
24
|
-
"**/CNAME",
|
25
29
|
"**/Cargo.toml",
|
30
|
+
"**/CNAME",
|
26
31
|
"**/Gemfile.lock",
|
27
|
-
".DS_Store",
|
28
|
-
".gitattributes",
|
29
|
-
".gitignore",
|
30
|
-
".gitleaksignore",
|
31
|
-
".pdd",
|
32
32
|
"Cargo.toml",
|
33
33
|
"Gemfile.lock",
|
34
|
-
"README.md",
|
35
34
|
"hardhat/.gitignore",
|
36
35
|
"hardhat/package.json",
|
36
|
+
"README.md",
|
37
37
|
"renovate.json",
|
38
38
|
]
|
39
39
|
precedence = "override"
|
data/hardhat/Dockerfile
CHANGED
@@ -14,14 +14,12 @@ RUN npm install
|
|
14
14
|
ARG PORT=8080
|
15
15
|
ARG HOST=localhost
|
16
16
|
COPY hardhat.config.js .
|
17
|
-
RUN sed -i "s/PORT/$PORT/g" hardhat.config.js
|
18
|
-
|
17
|
+
RUN sed -i "s/PORT/$PORT/g" hardhat.config.js \
|
18
|
+
&& sed -i "s/HOST/$HOST/g" hardhat.config.js
|
19
19
|
|
20
20
|
COPY contracts contracts
|
21
21
|
COPY ignition ignition
|
22
22
|
|
23
|
-
RUN rm -rf ignition/deployments
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
RUN rm -rf cache
|
23
|
+
RUN rm -rf ignition/deployments \
|
24
|
+
&& npx hardhat compile \
|
25
|
+
&& rm -rf cache
|
data/lib/erc20/erc20.rb
CHANGED
data/lib/erc20/fake_wallet.rb
CHANGED
@@ -69,6 +69,14 @@ class ERC20::FakeWallet
|
|
69
69
|
b
|
70
70
|
end
|
71
71
|
|
72
|
+
# Get ERC20 amount (in tokens) that was sent in the given transaction.
|
73
|
+
#
|
74
|
+
# @param [String] txn Hex of transaction
|
75
|
+
# @return [Integer] Balance, in ERC20 tokens
|
76
|
+
def sum_of(_txn)
|
77
|
+
42_000_000
|
78
|
+
end
|
79
|
+
|
72
80
|
# How much gas units is required in order to send ERC20 transaction.
|
73
81
|
#
|
74
82
|
# @param [String] from The departing address, in hex
|
data/lib/erc20/wallet.rb
CHANGED
@@ -142,6 +142,28 @@ class ERC20::Wallet
|
|
142
142
|
b
|
143
143
|
end
|
144
144
|
|
145
|
+
# Get ERC20 amount (in tokens) that was sent in the given transaction.
|
146
|
+
#
|
147
|
+
# @param [String] txn Hex of transaction
|
148
|
+
# @return [Integer] Balance, in ERC20 tokens
|
149
|
+
def sum_of(txn)
|
150
|
+
raise 'Transaction hash can\'t be nil' unless txn
|
151
|
+
raise 'Transaction hash must be a String' unless txn.is_a?(String)
|
152
|
+
raise 'Invalid format of the transaction hash' unless /^0x[0-9a-fA-F]{64}$/.match?(txn)
|
153
|
+
receipt = jsonrpc.eth_getTransactionReceipt(txn)
|
154
|
+
raise "Transaction not found: #{txn}" if receipt.nil?
|
155
|
+
logs = receipt['logs'] || []
|
156
|
+
transfer_event = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
|
157
|
+
logs.each do |log|
|
158
|
+
next unless log['topics'] && log['topics'][0] == transfer_event
|
159
|
+
next unless log['address'].downcase == @contract.downcase
|
160
|
+
amount = log['data'].to_i(16)
|
161
|
+
log_it(:debug, "Found transfer of #{amount} tokens in transaction #{txn}")
|
162
|
+
return amount
|
163
|
+
end
|
164
|
+
raise "No transfer event found in transaction #{txn}"
|
165
|
+
end
|
166
|
+
|
145
167
|
# How many gas units are required to send an ERC20 transaction.
|
146
168
|
#
|
147
169
|
# @param [String] from The sending address, in hex
|
@@ -312,9 +334,11 @@ class ERC20::Wallet
|
|
312
334
|
raise 'Addresses can\'t be nil' unless addresses
|
313
335
|
raise 'Addresses must respond to .to_a()' unless addresses.respond_to?(:to_a)
|
314
336
|
raise 'Active can\'t be nil' unless active
|
337
|
+
raise 'Active must respond to .to_a()' unless active.respond_to?(:to_a)
|
315
338
|
raise 'Active must respond to .append()' unless active.respond_to?(:append)
|
339
|
+
raise 'Active must respond to .clear()' unless active.respond_to?(:clear)
|
316
340
|
raise 'Delay must be an Integer' unless delay.is_a?(Integer)
|
317
|
-
raise 'Delay must be a positive Integer' unless delay.positive?
|
341
|
+
raise 'Delay must be a positive Integer or positive Float' unless delay.positive?
|
318
342
|
raise 'Subscription ID must be an Integer' unless subscription_id.is_a?(Integer)
|
319
343
|
raise 'Subscription ID must be a positive Integer' unless subscription_id.positive?
|
320
344
|
EventMachine.run do
|
@@ -336,37 +360,38 @@ class ERC20::Wallet
|
|
336
360
|
contract = @contract
|
337
361
|
log_url = "ws#{@ssl ? 's' : ''}://#{u.hostname}:#{u.port}"
|
338
362
|
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
|
363
|
+
timer = nil
|
366
364
|
ws.on(:open) do
|
367
365
|
safe do
|
368
366
|
verbose do
|
369
367
|
log_it(:debug, "Connected ##{subscription_id} to #{log_url}")
|
368
|
+
timer =
|
369
|
+
EventMachine.add_periodic_timer(delay) do
|
370
|
+
next if active.to_a.sort == addresses.to_a.sort
|
371
|
+
ws.send(
|
372
|
+
{
|
373
|
+
jsonrpc: '2.0',
|
374
|
+
id: subscription_id,
|
375
|
+
method: 'eth_subscribe',
|
376
|
+
params: [
|
377
|
+
'logs',
|
378
|
+
{
|
379
|
+
address: contract,
|
380
|
+
topics: [
|
381
|
+
'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
|
382
|
+
nil,
|
383
|
+
addresses.to_a.map { |a| "0x000000000000000000000000#{a[2..]}" }
|
384
|
+
]
|
385
|
+
}
|
386
|
+
]
|
387
|
+
}.to_json
|
388
|
+
)
|
389
|
+
log_it(
|
390
|
+
:debug,
|
391
|
+
"Requested to subscribe ##{subscription_id} to #{addresses.to_a.size} addresses: " \
|
392
|
+
"#{addresses.to_a.map { |a| a[0..6] }.join(', ')}"
|
393
|
+
)
|
394
|
+
end
|
370
395
|
end
|
371
396
|
end
|
372
397
|
end
|
@@ -411,9 +436,8 @@ class ERC20::Wallet
|
|
411
436
|
safe do
|
412
437
|
verbose do
|
413
438
|
log_it(:debug, "Disconnected ##{subscription_id} from #{log_url}")
|
414
|
-
sleep(delay)
|
415
439
|
active.clear
|
416
|
-
timer
|
440
|
+
timer&.cancel
|
417
441
|
reaccept(addresses, active, raw:, delay:, subscription_id: subscription_id + 1, &)
|
418
442
|
end
|
419
443
|
end
|
@@ -68,6 +68,11 @@ class TestFakeWallet < ERC20::Test
|
|
68
68
|
assert_equal(b, w.eth_balance(a))
|
69
69
|
end
|
70
70
|
|
71
|
+
def test_reads_sum_of_payment
|
72
|
+
txn = '0xcf0598d640d4bea3367e6af28a08c54342a39156afd292a31453778e4755945d'
|
73
|
+
assert_predicate(ERC20::FakeWallet.new.sum_of(txn), :positive?)
|
74
|
+
end
|
75
|
+
|
71
76
|
def test_returns_host
|
72
77
|
assert_equal('example.com', ERC20::FakeWallet.new.host)
|
73
78
|
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
|
@@ -162,6 +103,15 @@ class TestWallet < ERC20::Test
|
|
162
103
|
end
|
163
104
|
end
|
164
105
|
|
106
|
+
def test_reads_payment_amount_on_hardhat
|
107
|
+
WebMock.enable_net_connect!
|
108
|
+
on_hardhat do |wallet|
|
109
|
+
sum = 33_330
|
110
|
+
txn = wallet.pay(JEFF, Eth::Key.new(priv: WALTER).address.to_s, sum)
|
111
|
+
assert_equal(sum, wallet.sum_of(txn))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
165
115
|
def test_eth_pays_on_hardhat
|
166
116
|
WebMock.enable_net_connect!
|
167
117
|
on_hardhat do |wallet|
|
@@ -379,26 +329,6 @@ class TestWallet < ERC20::Test
|
|
379
329
|
end
|
380
330
|
end
|
381
331
|
|
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
332
|
def test_checks_balance_via_proxy
|
403
333
|
WebMock.enable_net_connect!
|
404
334
|
b = nil
|
@@ -410,151 +340,4 @@ class TestWallet < ERC20::Test
|
|
410
340
|
end
|
411
341
|
assert_equal(123_000_100_000, b)
|
412
342
|
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
343
|
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.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: eth
|
@@ -102,19 +102,21 @@ executables:
|
|
102
102
|
- erc20
|
103
103
|
extensions: []
|
104
104
|
extra_rdoc_files:
|
105
|
-
- README.md
|
106
105
|
- LICENSE.txt
|
106
|
+
- README.md
|
107
107
|
files:
|
108
108
|
- ".0pdd.yml"
|
109
109
|
- ".gitattributes"
|
110
110
|
- ".github/workflows/actionlint.yml"
|
111
111
|
- ".github/workflows/codecov.yml"
|
112
112
|
- ".github/workflows/copyrights.yml"
|
113
|
+
- ".github/workflows/hadolint.yml"
|
113
114
|
- ".github/workflows/markdown-lint.yml"
|
114
115
|
- ".github/workflows/pdd.yml"
|
115
116
|
- ".github/workflows/rake.yml"
|
116
117
|
- ".github/workflows/reuse.yml"
|
117
118
|
- ".github/workflows/shellcheck.yml"
|
119
|
+
- ".github/workflows/typos.yml"
|
118
120
|
- ".github/workflows/xcop.yml"
|
119
121
|
- ".github/workflows/yamllint.yml"
|
120
122
|
- ".gitignore"
|
@@ -149,6 +151,7 @@ files:
|
|
149
151
|
- renovate.json
|
150
152
|
- test/erc20/test_fake_wallet.rb
|
151
153
|
- test/erc20/test_wallet.rb
|
154
|
+
- test/erc20/test_wallet_live.rb
|
152
155
|
- test/test__helper.rb
|
153
156
|
homepage: http://github.com/yegor256/erc20.rb
|
154
157
|
licenses:
|
@@ -170,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
170
173
|
- !ruby/object:Gem::Version
|
171
174
|
version: '0'
|
172
175
|
requirements: []
|
173
|
-
rubygems_version: 3.6.
|
176
|
+
rubygems_version: 3.6.7
|
174
177
|
specification_version: 4
|
175
178
|
summary: Sending and receiving ERC20 tokens in Ethereum network
|
176
179
|
test_files: []
|