etherscan 0.2.1 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/README.md +15 -0
- data/Rakefile +12 -0
- data/lib/etherscan/api.rb +191 -0
- data/lib/etherscan/version.rb +5 -0
- data/lib/etherscan.rb +85 -0
- metadata +12 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e12314597d854f9d264641830082a829f3f99f40a49cbfa1283cac848527416
|
4
|
+
data.tar.gz: 825813db2b4c6debd2cf9c96a59ebbe8f5aacf13cdb84f5cbd9ecd85f44fd814
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ddbff16ed0c4d471b68bdccd621b1bb6c0f7a70209e93aa5d340f131f0f2e194cb58410b8cb2e50106f91f5a8b68ea75e104f1981a4ef49f53ec1d39a627acc
|
7
|
+
data.tar.gz: 368ab98f97fff3c59808fe6433029bc41b2dd59e60fb0776cfc8d8b863c5461bbec194040371ca9b4f584fcdb249b755903bf48163dc1527c5eb74ab6dda5e0d
|
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
module Etherscan
|
2
|
+
class Api
|
3
|
+
def initialize(url, api_key = nil)
|
4
|
+
@url = url
|
5
|
+
@api_key = api_key
|
6
|
+
end
|
7
|
+
|
8
|
+
def request(module_name, action, params = {})
|
9
|
+
params = params.reject { |_k, v| v.nil? } # filter out nil values
|
10
|
+
params_query = params.keys.map { |key| "#{key}=#{params[key]}" }.join('&').strip
|
11
|
+
params_query = if params_query.empty?
|
12
|
+
''
|
13
|
+
else
|
14
|
+
"&#{params_query}"
|
15
|
+
end
|
16
|
+
|
17
|
+
api_key_query = @api_key.nil? ? '' : "&apikey=#{@api_key}"
|
18
|
+
|
19
|
+
uri = URI "#{@url}?module=#{module_name}&action=#{action}#{params_query}#{api_key_query}"
|
20
|
+
Etherscan.logger.debug "Req: #{uri}"
|
21
|
+
resp = Net::HTTP.get(uri)
|
22
|
+
Etherscan.logger.debug "Rsp: #{resp}"
|
23
|
+
resp = JSON.parse(resp)
|
24
|
+
|
25
|
+
raise resp['result'] if resp['status'] == '0'
|
26
|
+
|
27
|
+
resp['result']
|
28
|
+
end
|
29
|
+
|
30
|
+
def respond_to_missing?(*_args)
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_missing(method, *args)
|
35
|
+
module_name, action = method.to_s.split('_')
|
36
|
+
request(module_name, action, args[0])
|
37
|
+
end
|
38
|
+
|
39
|
+
#########################################
|
40
|
+
# Accounts
|
41
|
+
#########################################
|
42
|
+
def account_balance(address:, tag: 'latest')
|
43
|
+
request('account', 'balance', address: address, tag: tag)
|
44
|
+
end
|
45
|
+
|
46
|
+
def account_balancemulti(addresses:, tag: 'latest')
|
47
|
+
if addresses.is_a? Array
|
48
|
+
request('account', 'balancemulti', address: addresses.join(','), tag: tag)
|
49
|
+
elsif addresses.is_a? String
|
50
|
+
request('account', 'balancemulti', address: addresses, tag: tag)
|
51
|
+
else
|
52
|
+
raise 'addresses must be an array or a string'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def account_txlist(address:, startblock: 0, endblock: 999_999_999, sort: 'asc', page: 1, offset: 1000)
|
57
|
+
request('account', 'txlist', address: address,
|
58
|
+
startblock: startblock,
|
59
|
+
endblock: endblock,
|
60
|
+
sort: sort,
|
61
|
+
page: page,
|
62
|
+
offset: offset)
|
63
|
+
end
|
64
|
+
|
65
|
+
def account_txlistinternal(address: nil,
|
66
|
+
startblock: 0, endblock: 999_999_999, sort: 'asc', page: 1, offset: 1000)
|
67
|
+
params = {
|
68
|
+
address: address,
|
69
|
+
startblock: startblock, endblock: endblock, sort: sort, page: page, offset: offset
|
70
|
+
}
|
71
|
+
request('account', 'txlistinternal', params)
|
72
|
+
end
|
73
|
+
|
74
|
+
def account_txlistinternal_by_txhash(txhash:)
|
75
|
+
# https://docs.etherscan.io/api-endpoints/accounts#get-internal-transactions-by-transaction-hash
|
76
|
+
request('account', 'txlistinternal', txhash: txhash)
|
77
|
+
end
|
78
|
+
|
79
|
+
def account_tokentx(address:, contractaddress:,
|
80
|
+
startblock: 0, endblock: 999_999_999, sort: 'asc', page: 1, offset: 1000)
|
81
|
+
params = {
|
82
|
+
address: address, contractaddress: contractaddress,
|
83
|
+
startblock: startblock, endblock: endblock, sort: sort, page: page, offset: offset
|
84
|
+
}
|
85
|
+
request('account', 'tokentx', params)
|
86
|
+
end
|
87
|
+
|
88
|
+
def account_tokennfttx(address:, contractaddress:,
|
89
|
+
startblock: 0, endblock: 999_999_999, sort: 'asc', page: 1, offset: 1000)
|
90
|
+
params = {
|
91
|
+
address: address, contractaddress: contractaddress,
|
92
|
+
startblock: startblock, endblock: endblock, sort: sort, page: page, offset: offset
|
93
|
+
}
|
94
|
+
request('account', 'tokennfttx', params)
|
95
|
+
end
|
96
|
+
|
97
|
+
#########################################
|
98
|
+
# Contracts
|
99
|
+
#########################################
|
100
|
+
def contract_getabi(address:)
|
101
|
+
request('contract', 'getabi', address: address)
|
102
|
+
end
|
103
|
+
|
104
|
+
def contract_getsourcecode(address:)
|
105
|
+
request('contract', 'getsourcecode', address: address)
|
106
|
+
end
|
107
|
+
|
108
|
+
def contract_getcontractcreation(contractaddresses:)
|
109
|
+
if contractaddresses.is_a? Array
|
110
|
+
request('contract', 'getcontractcreation', contractaddresses: contractaddresses.join(','))
|
111
|
+
elsif contractaddresses.is_a? String
|
112
|
+
request('contract', 'getcontractcreation', contractaddresses: contractaddresses)
|
113
|
+
else
|
114
|
+
raise 'contractaddresses must be an array or a string'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
#########################################
|
119
|
+
# Transactions
|
120
|
+
#########################################
|
121
|
+
def transaction_getstatus(txhash:)
|
122
|
+
request('transaction', 'getstatus', txhash: txhash)
|
123
|
+
end
|
124
|
+
|
125
|
+
def transaction_gettxreceiptstatus(txhash:)
|
126
|
+
request('transaction', 'gettxreceiptstatus', txhash: txhash)
|
127
|
+
end
|
128
|
+
|
129
|
+
#########################################
|
130
|
+
# Blocks
|
131
|
+
#########################################
|
132
|
+
def block_getblockreward(blockno:)
|
133
|
+
request('block', 'getblockreward', blockno: blockno)
|
134
|
+
end
|
135
|
+
|
136
|
+
def block_getblockcount(blockno:)
|
137
|
+
request('block', 'getblockcount', blockno: blockno)
|
138
|
+
end
|
139
|
+
|
140
|
+
def block_getblocknobytime(timestamp:, closest: 'before')
|
141
|
+
request('block', 'getblocknobytime', timestamp: timestamp, closest: closest)
|
142
|
+
end
|
143
|
+
|
144
|
+
#########################################
|
145
|
+
# Logs
|
146
|
+
#########################################
|
147
|
+
# fromBlock, toBlock, address
|
148
|
+
# topic0, topic1, topic2, topic3 (32 Bytes per topic)
|
149
|
+
# topic0_1_opr (and|or between topic0 & topic1),
|
150
|
+
# topic1_2_opr (and|or between topic1 & topic2),
|
151
|
+
# topic2_3_opr (and|or between topic2 & topic3),
|
152
|
+
# topic0_2_opr (and|or between topic0 & topic2),
|
153
|
+
# topic0_3_opr (and|or between topic0 & topic3),
|
154
|
+
# topic1_3_opr (and|or between topic1 & topic3)
|
155
|
+
def logs_getLogs(from_block: 0, to_block: 'latest', address: nil,
|
156
|
+
topic0: nil, topic1: nil, topic2: nil, topic3: nil,
|
157
|
+
topic0_1_opr: nil, topic1_2_opr: nil, topic2_3_opr: nil,
|
158
|
+
topic0_2_opr: nil, topic0_3_opr: nil, topic1_3_opr: nil)
|
159
|
+
params = {
|
160
|
+
fromBlock: from_block, toBlock: to_block, address: address,
|
161
|
+
topic0: topic0, topic1: topic1, topic2: topic2, topic3: topic3,
|
162
|
+
topic0_1_opr: topic0_1_opr, topic1_2_opr: topic1_2_opr, topic2_3_opr: topic2_3_opr,
|
163
|
+
topic0_2_opr: topic0_2_opr, topic0_3_opr: topic0_3_opr, topic1_3_opr: topic1_3_opr
|
164
|
+
}
|
165
|
+
|
166
|
+
request('logs', 'getLogs', params)
|
167
|
+
end
|
168
|
+
|
169
|
+
#########################################
|
170
|
+
# Tokens
|
171
|
+
#########################################
|
172
|
+
def stats_tokensupply(contractaddress:)
|
173
|
+
request('stats', 'tokensupply', contractaddress: contractaddress)
|
174
|
+
end
|
175
|
+
|
176
|
+
def account_tokenbalance(contractaddress:, address:, tag: 'latest')
|
177
|
+
request('account', 'tokenbalance', contractaddress: contractaddress, address: address, tag: tag)
|
178
|
+
end
|
179
|
+
|
180
|
+
#########################################
|
181
|
+
# Stats
|
182
|
+
#########################################
|
183
|
+
def stats_ethsupply
|
184
|
+
request('stats', 'ethsupply')
|
185
|
+
end
|
186
|
+
|
187
|
+
def stats_ethprice
|
188
|
+
request('stats', 'ethprice')
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
data/lib/etherscan.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'logger'
|
3
|
+
require 'net/http'
|
4
|
+
require 'etherscan/api'
|
5
|
+
|
6
|
+
require 'active_support'
|
7
|
+
require 'active_support/core_ext/string'
|
8
|
+
|
9
|
+
module Etherscan
|
10
|
+
# https://chainid.network/chains.json
|
11
|
+
# key is the underscore(short name) of the chain in chains.json
|
12
|
+
# https://blockscan.com/
|
13
|
+
CHAINS = {
|
14
|
+
'eth' => 'http://api.etherscan.io/api',
|
15
|
+
'gor' => 'https://api-goerli.etherscan.io/api',
|
16
|
+
'sep' => 'https://api-sepolia.etherscan.io/api',
|
17
|
+
'arb1' => 'https://api.arbiscan.io/api',
|
18
|
+
'arb_goerli' => 'https://api-goerli.arbiscan.io/api',
|
19
|
+
'arb_sep' => 'https://api-sepolia.arbiscan.io/api',
|
20
|
+
'bnb' => 'https://api.bscscan.com/api',
|
21
|
+
'bnbt' => 'https://api-testnet.bscscan.com/api',
|
22
|
+
'matic' => 'https://api.polygonscan.com/api',
|
23
|
+
'maticmum' => 'https://api-testnet.polygonscan.com/api',
|
24
|
+
'zkevm' => 'https://api-zkevm.polygonscan.com/api',
|
25
|
+
'testnet_zk_evm_mango' => 'https://api-testnet-zkevm.polygonscan.com/api',
|
26
|
+
'ftm' => 'https://api.ftmscan.com/api',
|
27
|
+
'tftm' => 'https://api-testnet.ftmscan.com/api',
|
28
|
+
'oeth' => 'https://api-optimistic.etherscan.io/api',
|
29
|
+
'mbeam' => 'https://api-moonbeam.moonscan.io/api',
|
30
|
+
'mriver' => 'https://api-moonriver.moonscan.io/api',
|
31
|
+
'mbase' => 'https://api-moonbase.moonscan.io/api',
|
32
|
+
'cronos' => 'https://api.cronoscan.com/api',
|
33
|
+
'btt' => 'https://api.bttcscan.com/api',
|
34
|
+
'tbtt' => 'https://api-testnet.bttcscan.com/api',
|
35
|
+
'gnosis' => 'https://api.gnosisscan.io/api',
|
36
|
+
'arb_nova' => 'https://api-nova.arbiscan.io/api',
|
37
|
+
# 'celo' => 'https://api.celoscan.io',
|
38
|
+
# 'alfa' => 'https://api-alfajores.celoscan.io',
|
39
|
+
'linea' => 'https://api.lineascan.build/api',
|
40
|
+
'linea_testnet' => 'https://api-testnet.lineascan.build/api',
|
41
|
+
'base' => 'https://api.basescan.org/api',
|
42
|
+
'basegor' => 'https://api-goerli.basescan.org/api',
|
43
|
+
'wemix' => 'https://api.wemixscan.com/api',
|
44
|
+
'scr' => 'https://api.scrollscan.com/api',
|
45
|
+
'scr_sepolia' => 'https://api-sepolia.scrollscan.com/api',
|
46
|
+
'obnb' => 'https://api-opbnb.bscscan.com/api'
|
47
|
+
}
|
48
|
+
|
49
|
+
class << self
|
50
|
+
attr_accessor :logger
|
51
|
+
|
52
|
+
def api(chain_short_name, api_key = nil)
|
53
|
+
url = CHAINS[chain_short_name]
|
54
|
+
url = CHAINS[chain_short_name.underscore] if url.nil?
|
55
|
+
raise "Chain `#{chain_short_name}` is not supported. Only #{CHAINS.keys} are supported." if url.nil?
|
56
|
+
|
57
|
+
Etherscan::Api.new(url, api_key)
|
58
|
+
end
|
59
|
+
|
60
|
+
# for example: Etherscan.eth('your_api_key')
|
61
|
+
# just a wrap of Etherscan.api('eth', 'your_api_key')
|
62
|
+
def method_missing(method, *args)
|
63
|
+
chain_short_name = method.to_s.downcase
|
64
|
+
api_key = args[0]
|
65
|
+
|
66
|
+
api(chain_short_name, api_key)
|
67
|
+
end
|
68
|
+
|
69
|
+
def respond_to_missing?(*_args)
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_all
|
74
|
+
CHAINS.reverse_each do |chain_short_name, url|
|
75
|
+
api = Etherscan::Api.new(url)
|
76
|
+
address = '0x0000000000000000000000000000000000000000'
|
77
|
+
balance = api.account_balance(address: address)
|
78
|
+
puts "#{chain_short_name} 0x0 balance: #{balance}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
Etherscan.logger = Logger.new($stdout)
|
85
|
+
Etherscan.logger.level = Logger::INFO
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: etherscan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aki Wu
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-12-
|
11
|
+
date: 2023-12-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -30,11 +30,18 @@ email:
|
|
30
30
|
executables: []
|
31
31
|
extensions: []
|
32
32
|
extra_rdoc_files: []
|
33
|
-
files:
|
33
|
+
files:
|
34
|
+
- Gemfile
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- lib/etherscan.rb
|
38
|
+
- lib/etherscan/api.rb
|
39
|
+
- lib/etherscan/version.rb
|
34
40
|
homepage: https://github.com/wuminzhe/etherscan
|
35
41
|
licenses:
|
36
42
|
- MIT
|
37
|
-
metadata:
|
43
|
+
metadata:
|
44
|
+
homepage_uri: https://github.com/wuminzhe/etherscan
|
38
45
|
post_install_message:
|
39
46
|
rdoc_options: []
|
40
47
|
require_paths:
|
@@ -43,7 +50,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
43
50
|
requirements:
|
44
51
|
- - ">="
|
45
52
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
53
|
+
version: 2.6.0
|
47
54
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
55
|
requirements:
|
49
56
|
- - ">="
|