erc20 0.2.8 → 0.2.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34ffb8c4c36d85d7afb74f0a573cc97cb58502ddcb5b39cdd032f771b9547592
4
- data.tar.gz: 5bb3efad4575d96a7d5fc0a3abc58b79835ec1954177dd774858793431595ce8
3
+ metadata.gz: bdb92899afb7e0f3ac74c59c07cdf1f48441d8b8bab6c96fadffe506c49dcedd
4
+ data.tar.gz: fd0faec13d2aafdd1cbd02afd0066c17b308b96637469f60eca0cbd1d5102629
5
5
  SHA512:
6
- metadata.gz: 4db054bf7264d2df18927384fdbbf6d72152aab5103456e319143681cc97653b7d2236941dbb26961f7de3b9752d7e057382f3695d63e7b3ba4ae92f78e8f00b
7
- data.tar.gz: 5eb68447c8736c39125d19daf27c5856ca7122b3b26784f024efc2fadcf1d5f55b6bb3ff088e335ef29cf55facfa8b734e42d9bb42a28aff78826c6e99dd2d28
6
+ metadata.gz: 509bdbaa8838f52ad6ea7d8a779001c1bfd87ba9c448ce4b808849967575069b8452e3a1f50b506c38f282b709e31e690a31b4247452b34d8cc097caa72e3b45
7
+ data.tar.gz: 8fa48303af7a75dd4019062bb9d3904c84f76f16dd994ee95d3d6cb0f952c9e207030be9063781c3224af6d0a3cde0f107667cdd4447c9fdc4108a05725b3bc3
data/Gemfile.lock CHANGED
@@ -8,7 +8,7 @@ PATH
8
8
  json (~> 2.10)
9
9
  jsonrpc-client (~> 0.1)
10
10
  loog (~> 0.4)
11
- slop (~> 4.0)
11
+ thor (~> 1.3)
12
12
 
13
13
  GEM
14
14
  remote: https://rubygems.org/
@@ -189,12 +189,12 @@ GEM
189
189
  simplecov (~> 0.19)
190
190
  simplecov-html (0.13.2)
191
191
  simplecov_json_formatter (0.1.4)
192
- slop (4.10.1)
193
192
  stringio (3.2.0)
194
193
  sys-uname (1.4.1)
195
194
  ffi (~> 1.1)
196
195
  memoist3 (~> 1.0.0)
197
- tago (0.6.0)
196
+ tago (0.7.0)
197
+ thor (1.5.0)
198
198
  threads (0.5.0)
199
199
  backtrace (~> 0)
200
200
  concurrent-ruby (~> 1.0)
data/README.md CHANGED
@@ -122,7 +122,7 @@ gem install erc20
122
122
  Then, run it:
123
123
 
124
124
  ```bash
125
- erc20 --help
125
+ erc20 help
126
126
  ```
127
127
 
128
128
  Usage should be straightforward. If you have questions, please submit an issue.
data/bin/erc20 CHANGED
@@ -8,126 +8,140 @@ $stdout.sync = true
8
8
 
9
9
  require 'backtrace'
10
10
  require 'loog'
11
- require 'slop'
11
+ require 'thor'
12
12
  require_relative '../lib/erc20'
13
13
  require_relative '../lib/erc20/erc20'
14
14
  require_relative '../lib/erc20/wallet'
15
15
 
16
- begin
17
- begin
18
- opts = Slop.parse(ARGV, strict: true, help: true) do |o|
19
- o.banner = "Usage (#{ERC20::VERSION}): erc20 [options] command [args]
20
- Commands are:
21
- key: Generate a new Ethereum private key (64 symbols)
22
- address: Turn private key into a public address (44 symbols)
23
- price: Get current price of one gas unit, in gwei
24
- pay: Send ERC20 payment
25
- eth_pay: Send ETH payment
26
- balance: Get ERC20 balance
27
- eth_balance: Get ETH balance
28
- Options are:"
29
- o.string(
30
- '--contract',
31
- 'Public address of ERC20 contract',
32
- default: ERC20::Wallet::USDT
33
- )
34
- o.integer(
35
- '--chain',
36
- 'Ethereum chain ID',
37
- default: 1
38
- )
39
- o.bool(
40
- '--ssl',
41
- 'Use SSL for HTTP connections',
42
- default: true
43
- )
44
- o.string(
45
- '--host',
46
- 'Host name of the provider',
47
- default: 'eth.llamarpc.com'
48
- )
49
- o.string(
50
- '--port',
51
- 'TCP port of the provider',
52
- default: 443
53
- )
54
- o.string(
55
- '--http_path',
56
- 'URL path for the HTTP RPC entry point of the provider',
57
- default: '/'
58
- )
59
- o.string(
60
- '--ws_path',
61
- 'URL path for the Websockets entry point of the provider',
62
- default: '/'
63
- )
64
- o.string(
65
- '--proxy',
66
- 'HTTP/S proxy for all requests, e.g. "localhost:3128"'
67
- )
68
- o.integer(
69
- '--attempts',
70
- 'How many times should we try before failing',
71
- default: 1
72
- )
73
- o.bool(
74
- '--dry',
75
- 'Don\'t send a real payment, run in a read-only mode'
76
- )
77
- o.bool('--help', 'Read this: https://github.com/yegor256/erc20') do
78
- puts o
79
- exit
80
- end
81
- o.bool('--verbose', 'Print all possible debug messages')
82
- end
83
- rescue Slop::Error => e
84
- raise e.message
16
+ # Command line interface for ERC20 operations.
17
+ #
18
+ # This CLI provides commands for managing ERC20 tokens and ETH on Ethereum:
19
+ # generating keys, checking balances, and sending payments.
20
+ #
21
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
22
+ # Copyright:: Copyright (c) 2025 Yegor Bugayenko
23
+ # License:: MIT
24
+ class Bin < Thor
25
+ def self.exit_on_failure?
26
+ true
85
27
  end
86
- raise 'Try --help' if opts.arguments.empty?
87
- log = opts[:verbose] ? Loog::VERBOSE : Loog::REGULAR
88
- wallet =
89
- if opts[:dry]
90
- ERC20::FakeWallet.new
91
- else
92
- ERC20::Wallet.new(
93
- contract: opts[:contract],
94
- host: opts[:host], port: opts[:port].to_i,
95
- http_path: opts[:http_path], ws_path: opts[:ws_path],
96
- ssl: opts[:ssl],
97
- log:
98
- )
99
- end
100
- case opts.arguments[0]
101
- when 'key'
102
- puts Eth::Key.new.private_hex.downcase
103
- when 'address'
104
- puts Eth::Key.new(priv: opts.arguments[1]).address.to_s.downcase
105
- when 'price'
106
- puts wallet.gas_price
107
- when 'balance'
108
- address = opts.arguments[1]
109
- raise 'Address is required' if address.nil?
28
+
29
+ class_option :contract, type: :string, default: ERC20::Wallet::USDT,
30
+ desc: 'Public address of ERC20 contract'
31
+ class_option :chain, type: :numeric, default: 1,
32
+ desc: 'Ethereum chain ID'
33
+ class_option :ssl, type: :boolean, default: true,
34
+ desc: 'Use SSL for HTTP connections'
35
+ class_option :host, type: :string, default: 'eth.llamarpc.com',
36
+ desc: 'Host name of the provider'
37
+ class_option :port, type: :numeric, default: 443,
38
+ desc: 'TCP port of the provider'
39
+ class_option :http_path, type: :string, default: '/',
40
+ desc: 'URL path for the HTTP RPC entry point of the provider'
41
+ class_option :ws_path, type: :string, default: '/',
42
+ desc: 'URL path for the Websockets entry point of the provider'
43
+ class_option :proxy, type: :string,
44
+ desc: 'HTTP/S proxy for all requests, e.g. "localhost:3128"'
45
+ class_option :attempts, type: :numeric, default: 1,
46
+ desc: 'How many times should we try before failing'
47
+ class_option :dry, type: :boolean, default: false,
48
+ desc: "Don't send a real payment, run in a read-only mode"
49
+ class_option :verbose, type: :boolean, default: false,
50
+ desc: 'Print all possible debug messages'
51
+
52
+ desc 'version', 'Print version of the tool'
53
+ def version
54
+ log.info(ERC20::VERSION)
55
+ end
56
+
57
+ desc 'key', 'Generate a new Ethereum private key (64 hex symbols)'
58
+ def key
59
+ log.info(Eth::Key.new.private_hex.downcase)
60
+ end
61
+
62
+ desc 'address KEY', 'Turn private key into a public address (44 hex symbols)'
63
+ def address(pvt)
64
+ log.info(Eth::Key.new(priv: pvt).address.to_s.downcase)
65
+ end
66
+
67
+ desc 'price', 'Get current price of one gas unit, in gwei'
68
+ def price
69
+ log.info(wallet.gas_price)
70
+ end
71
+
72
+ desc 'balance ADDRESS', 'Get ERC20 balance'
73
+ def balance(address)
110
74
  log.debug("Checking ERC20 balance of #{address}")
111
75
  tokens = wallet.balance(address)
112
76
  log.debug("The balance is #{tokens} ERC20 tokens (#{tokens.to_f / 1_000_000} USDT)")
113
- puts tokens
114
- when 'eth_balance'
115
- address = opts.arguments[1]
116
- raise 'Address is required' if address.nil?
77
+ log.info(tokens)
78
+ end
79
+
80
+ desc 'eth_balance ADDRESS', 'Get ETH balance'
81
+ def eth_balance(address)
117
82
  log.debug("Checking ETH balance of #{address}")
118
83
  wei = wallet.eth_balance(address)
119
84
  log.debug("The balance of #{address} is #{wei} wei (#{format('%0.4f', wei.to_f / 1_000_000_000_000_000_000)} ETH)")
120
- puts wei
121
- when 'pay'
122
- pkey = opts.arguments[1]
123
- raise 'Private key is required' if pkey.nil?
85
+ log.info(wei)
86
+ end
87
+
88
+ desc 'pay KEY ADDRESS AMOUNT', 'Send ERC20 payment'
89
+ def pay(pkey, address, amount)
124
90
  priv = Eth::Key.new(priv: pkey)
125
- log.debug("Sending ERC20 tokens from #{priv.address.to_s}")
126
- address = opts.arguments[2]
127
- raise 'Address is required' if address.nil?
91
+ log.debug("Sending ERC20 tokens from #{priv.address}")
128
92
  log.debug("Sending ERC20 tokens to #{address}")
129
- amount = opts.arguments[3]
130
- raise 'Amount argument is required (for example, "19.9usdt" or "$19.9")' if amount.nil?
93
+ amount = tokens(amount)
94
+ log.debug("Sending #{amount} ERC20 tokens")
95
+ if wallet.eth_balance(priv.address.to_s).zero?
96
+ log.debug("ETH balance of #{priv.address} is zero, the txn most definitely will be rejected by the server!")
97
+ end
98
+ log.info(wallet.pay(priv.private_hex, address, amount))
99
+ end
100
+
101
+ desc 'eth_pay KEY ADDRESS AMOUNT', 'Send ETH payment'
102
+ def eth_pay(pkey, address, amount)
103
+ priv = Eth::Key.new(priv: pkey)
104
+ log.debug("Sending ETH from #{priv.address}")
105
+ log.debug("Sending ETH to #{address}")
106
+ raise "Amount #{amount.inspect} is not valid" unless /^[0-9]+(\.[0-9]+)?(eth|wei|gwei)?$/.match?(amount)
107
+ amount = wei(amount)
108
+ log.debug("Sending #{amount} wei...")
109
+ log.info(wallet.eth_pay(priv.private_hex, address, amount))
110
+ end
111
+
112
+ def self.banner(task, namespace = false, subcommand = false)
113
+ "#{basename} #{task.usage}"
114
+ end
115
+
116
+ def self.help(shell, subcommand = false)
117
+ shell.say "Usage (#{ERC20::VERSION}): erc20 [options] command [args]"
118
+ shell.say 'Commands are:'
119
+ super
120
+ shell.say "\nRead this: https://github.com/yegor256/erc20"
121
+ end
122
+
123
+ private
124
+
125
+ def log
126
+ @log ||= options[:verbose] ? Loog::VERBOSE : Loog::REGULAR
127
+ end
128
+
129
+ def wallet
130
+ @wallet ||=
131
+ if options[:dry]
132
+ ERC20::FakeWallet.new
133
+ else
134
+ ERC20::Wallet.new(
135
+ contract: options[:contract],
136
+ host: options[:host], port: options[:port].to_i,
137
+ http_path: options[:http_path], ws_path: options[:ws_path],
138
+ ssl: options[:ssl],
139
+ log: log
140
+ )
141
+ end
142
+ end
143
+
144
+ def tokens(amount)
131
145
  if /^[0-9]+$/.match?(amount)
132
146
  amount = amount.to_i
133
147
  log.debug("The token amount equals to #{amount} ERC20 tokens (as provided)")
@@ -140,43 +154,29 @@ Options are:"
140
154
  else
141
155
  raise "Can't understand amount: #{amount.inspect}"
142
156
  end
143
- log.debug("Sending #{amount} ERC20 tokens")
144
- if wallet.eth_balance(priv.address.to_s).zero?
145
- log.debug("ETH balance of #{priv.address} is zero, the txn most definitely will be rejected by the server!")
146
- end
147
- puts wallet.pay(priv.private_hex, address, amount)
148
- when 'eth_pay'
149
- pkey = opts.arguments[1]
150
- raise 'Private key is required' if pkey.nil?
151
- priv = Eth::Key.new(priv: pkey)
152
- log.debug("Sending ETH from #{priv.address.to_s}")
153
- address = opts.arguments[2]
154
- raise 'Address is required' if address.nil?
155
- log.debug("Sending ETH to #{address}")
156
- amount = opts.arguments[3]
157
- raise 'Amount argument is required' if amount.nil?
158
- raise "Amount #{amount.inspect} is not valid" unless /^[0-9]+(\.[0-9]+)?(eth|wei|gwei)?$/.match?(amount)
157
+ amount
158
+ end
159
+
160
+ def wei(amount)
159
161
  if /^[0-9]+$/.match?(amount)
160
- amount = amount.to_i
161
- elsif /[0-9]wei+$/.match?(amount)
162
- amount = amount.gsub(/wei^/, '').to_i
163
- elsif /[0-9]gwei+$/.match?(amount)
164
- amount = (amount.gsub(/gwei^/, '').to_f * 1_000_000_000).to_i
165
- elsif /[0-9]eth+$/.match?(amount)
166
- amount = (amount.gsub(/eth^/, '').to_f * 1_000_000_000_000_000_000).to_i
162
+ amount.to_i
163
+ elsif /[0-9]wei$/.match?(amount)
164
+ amount.gsub(/wei$/, '').to_i
165
+ elsif /[0-9]gwei$/.match?(amount)
166
+ (amount.gsub(/gwei$/, '').to_f * 1_000_000_000).to_i
167
+ elsif /[0-9]eth$/.match?(amount)
168
+ (amount.gsub(/eth$/, '').to_f * 1_000_000_000_000_000_000).to_i
167
169
  else
168
170
  raise "Can't understand amount: #{amount.inspect}"
169
171
  end
170
- log.debug("Sending #{amount} wei...")
171
- puts wallet.eth_pay(priv.private_hex, address, amount)
172
- else
173
- raise "Command #{opts.arguments[0]} is not supported"
174
172
  end
173
+ end
174
+
175
+ begin
176
+ Bin.start(ARGV)
175
177
  rescue StandardError => e
176
- if opts[:verbose]
177
- puts Backtrace.new(e)
178
- else
179
- puts "ERROR: #{e.message}"
180
- end
178
+ log = ARGV.include?('--verbose') ? Loog::VERBOSE : Loog::REGULAR
179
+ log.debug(Backtrace.new(e))
180
+ log.error(e.message)
181
181
  exit(255)
182
182
  end
data/erc20.gemspec CHANGED
@@ -31,6 +31,6 @@ Gem::Specification.new do |s|
31
31
  s.add_dependency 'json', '~>2.10'
32
32
  s.add_dependency 'jsonrpc-client', '~>0.1'
33
33
  s.add_dependency 'loog', '~>0.4'
34
- s.add_dependency 'slop', '~>4.0'
34
+ s.add_dependency 'thor', '~>1.3'
35
35
  s.metadata['rubygems_mfa_required'] = 'true'
36
36
  end
data/features/cli.feature CHANGED
@@ -2,16 +2,12 @@
2
2
  # SPDX-License-Identifier: MIT
3
3
 
4
4
  Feature: Command Line Processing
5
- As a simple ETH/ERC20 user I want to send payments
5
+ As a simple ETH/ERC20 user I want to use it
6
6
 
7
7
  Scenario: Help can be printed
8
- When I run bin/erc20 with "--help"
9
- Then Exit code is zero
10
- And Stdout contains "--help"
11
-
12
- Scenario: Gas price price can be retrieved
13
- When I run bin/erc20 with "price --attempts=4"
8
+ When I run bin/erc20 with "help"
14
9
  Then Exit code is zero
10
+ And Stdout contains "help"
15
11
 
16
12
  Scenario: ETH private key can be generated
17
13
  When I run bin/erc20 with "key"
@@ -20,11 +16,3 @@ Feature: Command Line Processing
20
16
  Scenario: ETH address can be created
21
17
  When I run bin/erc20 with "address 46feba063e9b59a8ae0dba68abd39a3cb8f52089e776576d6eb1bb5bfec123d1"
22
18
  Then Exit code is zero
23
-
24
- Scenario: ERC20 balance can be checked
25
- When I run bin/erc20 with "balance 0x7232148927F8a580053792f44D4d59d40Fd00ABD --verbose"
26
- Then Exit code is zero
27
-
28
- Scenario: ETH balance can be checked
29
- When I run bin/erc20 with "eth_balance 0x7232148927F8a580053792f44D4d59d40Fd00ABD --verbose"
30
- Then Exit code is zero
@@ -0,0 +1,17 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ Feature: Command Line Processing
5
+ As a simple ETH/ERC20 user I want to use it live
6
+
7
+ Scenario: Gas price price can be retrieved
8
+ When I run bin/erc20 with "price --attempts=4"
9
+ Then Exit code is zero
10
+
11
+ Scenario: ERC20 balance can be checked
12
+ When I run bin/erc20 with "balance 0x7232148927F8a580053792f44D4d59d40Fd00ABD --verbose --attempts=4"
13
+ Then Exit code is zero
14
+
15
+ Scenario: ETH balance can be checked
16
+ When I run bin/erc20 with "eth_balance 0x7232148927F8a580053792f44D4d59d40Fd00ABD --verbose --attempts=4"
17
+ Then Exit code is zero
@@ -4,7 +4,6 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require 'tmpdir'
7
- require 'slop'
8
7
  require 'English'
9
8
  require_relative '../../lib/erc20'
10
9
 
@@ -13,10 +12,6 @@ Before do
13
12
  @dir = Dir.mktmpdir('test')
14
13
  FileUtils.mkdir_p(@dir)
15
14
  Dir.chdir(@dir)
16
- @opts =
17
- Slop.parse ['-v'] do |o|
18
- o.bool '-v', '--verbose'
19
- end
20
15
  end
21
16
 
22
17
  After do
data/lib/erc20/erc20.rb CHANGED
@@ -25,5 +25,5 @@
25
25
  # License:: MIT
26
26
  module ERC20
27
27
  # Current version of the gem (changed by the +.rultor.yml+ on every release)
28
- VERSION = '0.2.8'
28
+ VERSION = '0.2.9' unless defined?(VERSION)
29
29
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: erc20
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.2.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
@@ -94,19 +94,19 @@ dependencies:
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0.4'
96
96
  - !ruby/object:Gem::Dependency
97
- name: slop
97
+ name: thor
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '4.0'
102
+ version: '1.3'
103
103
  type: :runtime
104
104
  prerelease: false
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: '4.0'
109
+ version: '1.3'
110
110
  description: 'A simple library for making ERC20 manipulations as easy as they can
111
111
  be for cryptocurrency newbies: checking balance, sending payments, and monitoring
112
112
  addresses for incoming payments. The library expects Ethereum node to provide JSON
@@ -131,6 +131,7 @@ files:
131
131
  - features/cli.feature
132
132
  - features/dry.feature
133
133
  - features/gem_package.feature
134
+ - features/live.feature
134
135
  - features/step_definitions/steps.rb
135
136
  - features/support/env.rb
136
137
  - hardhat/.gitignore