Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
b4629bec
Unverified
Commit
b4629bec
authored
Nov 16, 2023
by
Wyatt Barnes
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Init etherscan client
parent
6e371b4b
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
222 additions
and
0 deletions
+222
-0
client.go
op-bindings/etherscan/client.go
+222
-0
No files found.
op-bindings/etherscan/client.go
0 → 100644
View file @
b4629bec
package
etherscan
import
(
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"time"
"github.com/ethereum-optimism/optimism/op-service/retry"
)
type
client
struct
{
baseUrl
string
httpClient
*
http
.
Client
}
type
apiResponse
struct
{
Status
string
`json:"status"`
Message
string
`json:"message"`
Result
json
.
RawMessage
`json:"result"`
}
type
rpcResponse
struct
{
JsonRpc
string
`json:"jsonrpc"`
Id
int
`json:"id"`
Result
json
.
RawMessage
`json:"result"`
}
type
TxInfo
struct
{
TxHash
string
`json:"txHash"`
To
string
`json:"to"`
Input
string
`json:"input"`
}
const
apiMaxRetries
=
3
const
apiRetryDelay
=
time
.
Duration
(
2
)
*
time
.
Second
const
errRateLimited
=
"Max rate limit reached"
func
NewClient
(
baseUrl
,
apiKey
string
)
*
client
{
return
&
client
{
baseUrl
:
baseUrl
+
"/api?apikey="
+
apiKey
+
"&"
,
httpClient
:
&
http
.
Client
{
Timeout
:
time
.
Second
*
10
,
},
}
}
func
NewEthereumClient
(
apiKey
string
)
*
client
{
return
NewClient
(
"https://api.etherscan.io"
,
apiKey
)
}
func
NewOptimismClient
(
apiKey
string
)
*
client
{
return
NewClient
(
"https://api-optimistic.etherscan.io"
,
apiKey
)
}
func
(
c
*
client
)
fetch
(
ctx
context
.
Context
,
url
string
)
([]
byte
,
error
)
{
req
,
err
:=
http
.
NewRequestWithContext
(
ctx
,
http
.
MethodGet
,
url
,
nil
)
if
err
!=
nil
{
return
nil
,
err
}
resp
,
err
:=
c
.
httpClient
.
Do
(
req
)
if
err
!=
nil
{
return
nil
,
err
}
defer
resp
.
Body
.
Close
()
body
,
err
:=
io
.
ReadAll
(
resp
.
Body
)
if
err
!=
nil
{
return
nil
,
err
}
return
body
,
nil
}
func
(
c
*
client
)
fetchEtherscanApi
(
ctx
context
.
Context
,
url
string
)
(
apiResponse
,
error
)
{
return
retry
.
Do
[
apiResponse
](
ctx
,
apiMaxRetries
,
retry
.
Fixed
(
apiRetryDelay
),
func
()
(
apiResponse
,
error
)
{
body
,
err
:=
c
.
fetch
(
ctx
,
url
)
if
err
!=
nil
{
return
apiResponse
{},
err
}
var
response
apiResponse
err
=
json
.
Unmarshal
(
body
,
&
response
)
if
err
!=
nil
{
return
apiResponse
{},
fmt
.
Errorf
(
"failed to unmarshal as apiResponse: %w"
,
err
)
}
if
response
.
Message
!=
"OK"
{
var
resultString
string
err
=
json
.
Unmarshal
(
response
.
Result
,
&
resultString
)
if
err
!=
nil
{
return
apiResponse
{},
fmt
.
Errorf
(
"response for %s not OK, returned message: %s"
,
url
,
response
.
Message
)
}
if
resultString
==
errRateLimited
{
return
apiResponse
{},
errors
.
New
(
errRateLimited
)
}
return
apiResponse
{},
fmt
.
Errorf
(
"there was an issue with the Etherscan request to %s, received response: %v"
,
url
,
response
)
}
return
response
,
nil
})
}
func
(
c
*
client
)
fetchEtherscanRpc
(
ctx
context
.
Context
,
url
string
)
(
rpcResponse
,
error
)
{
return
retry
.
Do
[
rpcResponse
](
ctx
,
apiMaxRetries
,
retry
.
Fixed
(
apiRetryDelay
),
func
()
(
rpcResponse
,
error
)
{
body
,
err
:=
c
.
fetch
(
ctx
,
url
)
if
err
!=
nil
{
return
rpcResponse
{},
err
}
var
response
rpcResponse
err
=
json
.
Unmarshal
(
body
,
&
response
)
if
err
!=
nil
{
return
rpcResponse
{},
fmt
.
Errorf
(
"failed to unmarshal as rpcResponse: %w"
,
err
)
}
var
resultString
string
_
=
json
.
Unmarshal
(
response
.
Result
,
&
resultString
)
if
err
!=
nil
{
return
rpcResponse
{},
fmt
.
Errorf
(
"failed to unmarshal response.Result as a string: %w"
,
err
)
}
if
resultString
==
errRateLimited
{
return
rpcResponse
{},
errors
.
New
(
errRateLimited
)
}
return
response
,
nil
})
}
func
(
c
*
client
)
FetchAbi
(
ctx
context
.
Context
,
address
string
)
(
string
,
error
)
{
params
:=
url
.
Values
{}
params
.
Set
(
"address"
,
address
)
url
:=
constructUrl
(
c
.
baseUrl
,
"getabi"
,
"contract"
,
params
)
response
,
err
:=
c
.
fetchEtherscanApi
(
ctx
,
url
)
if
err
!=
nil
{
return
""
,
err
}
var
abi
string
err
=
json
.
Unmarshal
(
response
.
Result
,
&
abi
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"API response result is not expected ABI string: %w"
,
err
)
}
return
abi
,
nil
}
func
(
c
*
client
)
FetchDeployedBytecode
(
ctx
context
.
Context
,
address
string
)
(
string
,
error
)
{
params
:=
url
.
Values
{}
params
.
Set
(
"address"
,
address
)
url
:=
constructUrl
(
c
.
baseUrl
,
"eth_getCode"
,
"proxy"
,
params
)
response
,
err
:=
c
.
fetchEtherscanRpc
(
ctx
,
url
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"error fetching deployed bytecode: %w"
,
err
)
}
var
bytecode
string
err
=
json
.
Unmarshal
(
response
.
Result
,
&
bytecode
)
if
err
!=
nil
{
return
""
,
errors
.
New
(
"API response result is not expected bytecode string"
)
}
return
bytecode
,
nil
}
func
(
c
*
client
)
FetchDeploymentTxHash
(
ctx
context
.
Context
,
address
string
)
(
string
,
error
)
{
params
:=
url
.
Values
{}
params
.
Set
(
"contractaddresses"
,
address
)
url
:=
constructUrl
(
c
.
baseUrl
,
"getcontractcreation"
,
"contract"
,
params
)
response
,
err
:=
c
.
fetchEtherscanApi
(
ctx
,
url
)
if
err
!=
nil
{
return
""
,
err
}
var
results
[]
TxInfo
err
=
json
.
Unmarshal
(
response
.
Result
,
&
results
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"failed to unmarshal API response as []txInfo: %w"
,
err
)
}
if
len
(
results
)
==
0
{
return
""
,
fmt
.
Errorf
(
"API response result is an empty array"
)
}
return
results
[
0
]
.
TxHash
,
nil
}
func
(
c
*
client
)
FetchDeploymentTx
(
ctx
context
.
Context
,
txHash
string
)
(
TxInfo
,
error
)
{
params
:=
url
.
Values
{}
params
.
Set
(
"txHash"
,
txHash
)
params
.
Set
(
"tag"
,
"latest"
)
url
:=
constructUrl
(
c
.
baseUrl
,
"eth_getTransactionByHash"
,
"proxy"
,
params
)
response
,
err
:=
c
.
fetchEtherscanRpc
(
ctx
,
url
)
if
err
!=
nil
{
return
TxInfo
{},
err
}
resultBytes
,
err
:=
json
.
Marshal
(
response
.
Result
)
if
err
!=
nil
{
return
TxInfo
{},
fmt
.
Errorf
(
"failed to marshal Result into JSON: %w"
,
err
)
}
var
tx
TxInfo
err
=
json
.
Unmarshal
(
resultBytes
,
&
tx
)
if
err
!=
nil
{
return
TxInfo
{},
fmt
.
Errorf
(
"API response result is not expected txInfo struct: %w"
,
err
)
}
return
tx
,
nil
}
func
constructUrl
(
baseUrl
,
action
,
module
string
,
params
url
.
Values
)
string
{
params
.
Set
(
"action"
,
action
)
params
.
Set
(
"module"
,
module
)
queryFragment
:=
params
.
Encode
()
return
baseUrl
+
queryFragment
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment