@@ -35,11 +35,13 @@ import (
3535 "github.com/ethereum/go-ethereum/common"
3636 "github.com/ethereum/go-ethereum/common/hexutil"
3737 "github.com/ethereum/go-ethereum/core/types"
38+ "golang.org/x/time/rate"
3839)
3940
40- // CallInterval is the duration between etherscan requests.
41+ // callsPerSec is thenumber of etherscanr equests allowed
42+ // per second.
4143// Etherscan rate limits to one request per 0.2 seconds.
42- var CallInterval = 260 * time . Millisecond
44+ var callsPerSec = 3.8
4345
4446const apiKey = "X3AFAGQT2QCAFTFPIH9VJY88H9PIQ2UWP7"
4547
@@ -50,17 +52,22 @@ const ERC20GasErr = "insufficient funds for gas * price + value"
5052type EtherScan struct {
5153 url string
5254 httpClient * http.Client
55+ limiter * rate.Limiter
5356}
5457
5558// NewEtherScan creates a new instance of EtherScan.
5659func NewEtherScan (url string , httpClient * http.Client ) * EtherScan {
5760 return & EtherScan {
5861 url : url ,
5962 httpClient : httpClient ,
63+ limiter : rate .NewLimiter (rate .Limit (callsPerSec ), 1 ),
6064 }
6165}
6266
63- func (etherScan * EtherScan ) call (params url.Values , result interface {}) error {
67+ func (etherScan * EtherScan ) call (ctx context.Context , params url.Values , result interface {}) error {
68+ if err := etherScan .limiter .Wait (ctx ); err != nil {
69+ return errp .WithStack (err )
70+ }
6471 params .Set ("apikey" , apiKey )
6572 response , err := etherScan .httpClient .Get (etherScan .url + "?" + params .Encode ())
6673 if err != nil {
@@ -323,7 +330,7 @@ func (etherScan *EtherScan) Transactions(
323330 result := struct {
324331 Result []* Transaction
325332 }{}
326- if err := etherScan .call (params , & result ); err != nil {
333+ if err := etherScan .call (context . TODO (), params , & result ); err != nil {
327334 return nil , err
328335 }
329336 isERC20 := erc20Token != nil
@@ -338,7 +345,7 @@ func (etherScan *EtherScan) Transactions(
338345 resultInternal := struct {
339346 Result []* Transaction
340347 }{}
341- if err := etherScan .call (params , & resultInternal ); err != nil {
348+ if err := etherScan .call (context . TODO (), params , & resultInternal ); err != nil {
342349 return nil , err
343350 }
344351 var err error
@@ -353,7 +360,7 @@ func (etherScan *EtherScan) Transactions(
353360
354361// ----- RPC node proxy methods follow
355362
356- func (etherScan * EtherScan ) rpcCall (params url.Values , result interface {}) error {
363+ func (etherScan * EtherScan ) rpcCall (ctx context. Context , params url.Values , result interface {}) error {
357364 params .Set ("module" , "proxy" )
358365
359366 var wrapped struct {
@@ -364,7 +371,7 @@ func (etherScan *EtherScan) rpcCall(params url.Values, result interface{}) error
364371 } `json:"error"`
365372 Result * json.RawMessage `json:"result"`
366373 }
367- if err := etherScan .call (params , & wrapped ); err != nil {
374+ if err := etherScan .call (ctx , params , & wrapped ); err != nil {
368375 return err
369376 }
370377 if wrapped .Error != nil {
@@ -389,7 +396,7 @@ func (etherScan *EtherScan) TransactionReceiptWithBlockNumber(
389396 params .Set ("action" , "eth_getTransactionReceipt" )
390397 params .Set ("txhash" , hash .Hex ())
391398 var result * rpcclient.RPCTransactionReceipt
392- if err := etherScan .rpcCall (params , & result ); err != nil {
399+ if err := etherScan .rpcCall (ctx , params , & result ); err != nil {
393400 return nil , err
394401 }
395402 return result , nil
@@ -402,7 +409,7 @@ func (etherScan *EtherScan) TransactionByHash(
402409 params .Set ("action" , "eth_getTransactionByHash" )
403410 params .Set ("txhash" , hash .Hex ())
404411 var result rpcclient.RPCTransaction
405- if err := etherScan .rpcCall (params , & result ); err != nil {
412+ if err := etherScan .rpcCall (ctx , params , & result ); err != nil {
406413 return nil , false , err
407414 }
408415 return & result .Transaction , result .BlockNumber == nil , nil
@@ -415,7 +422,7 @@ func (etherScan *EtherScan) BlockNumber(ctx context.Context) (*big.Int, error) {
415422 params .Set ("tag" , "latest" )
416423 params .Set ("boolean" , "false" )
417424 var header * types.Header
418- if err := etherScan .rpcCall (params , & header ); err != nil {
425+ if err := etherScan .rpcCall (ctx , params , & header ); err != nil {
419426 return nil , err
420427 }
421428 return header .Number , nil
@@ -434,7 +441,7 @@ func (etherScan *EtherScan) Balance(ctx context.Context, account common.Address)
434441 params .Set ("action" , "balance" )
435442 params .Set ("address" , account .Hex ())
436443 params .Set ("tag" , "latest" )
437- if err := etherScan .call (params , & result ); err != nil {
444+ if err := etherScan .call (ctx , params , & result ); err != nil {
438445 return nil , err
439446 }
440447 if result .Status != "1" {
@@ -461,7 +468,7 @@ func (etherScan *EtherScan) ERC20Balance(account common.Address, erc20Token *erc
461468 params .Set ("address" , account .Hex ())
462469 params .Set ("contractaddress" , erc20Token .ContractAddress ().Hex ())
463470 params .Set ("tag" , "latest" )
464- if err := etherScan .call (params , & result ); err != nil {
471+ if err := etherScan .call (context . TODO (), params , & result ); err != nil {
465472 return nil , err
466473 }
467474 if result .Status != "1" {
@@ -485,7 +492,7 @@ func (etherScan *EtherScan) CallContract(ctx context.Context, msg ethereum.CallM
485492 panic ("not implemented" )
486493 }
487494 var result hexutil.Bytes
488- if err := etherScan .rpcCall (params , & result ); err != nil {
495+ if err := etherScan .rpcCall (ctx , params , & result ); err != nil {
489496 return nil , err
490497 }
491498 return result , nil
@@ -515,7 +522,7 @@ func (etherScan *EtherScan) EstimateGas(ctx context.Context, msg ethereum.CallMs
515522 callMsgParams (& params , msg )
516523
517524 var result hexutil.Uint64
518- if err := etherScan .rpcCall (params , & result ); err != nil {
525+ if err := etherScan .rpcCall (ctx , params , & result ); err != nil {
519526 return 0 , err
520527 }
521528 return uint64 (result ), nil
@@ -528,7 +535,7 @@ func (etherScan *EtherScan) PendingNonceAt(ctx context.Context, account common.A
528535 params .Set ("address" , account .Hex ())
529536 params .Set ("tag" , "pending" )
530537 var result hexutil.Uint64
531- if err := etherScan .rpcCall (params , & result ); err != nil {
538+ if err := etherScan .rpcCall (ctx , params , & result ); err != nil {
532539 return 0 , err
533540 }
534541 return uint64 (result ), nil
@@ -544,15 +551,15 @@ func (etherScan *EtherScan) SendTransaction(ctx context.Context, tx *types.Trans
544551 params := url.Values {}
545552 params .Set ("action" , "eth_sendRawTransaction" )
546553 params .Set ("hex" , hexutil .Encode (encodedTx ))
547- return etherScan .rpcCall (params , nil )
554+ return etherScan .rpcCall (ctx , params , nil )
548555}
549556
550557// SuggestGasPrice implements rpc.Interface.
551558func (etherScan * EtherScan ) SuggestGasPrice (ctx context.Context ) (* big.Int , error ) {
552559 params := url.Values {}
553560 params .Set ("action" , "eth_gasPrice" )
554561 var result hexutil.Big
555- if err := etherScan .rpcCall (params , & result ); err != nil {
562+ if err := etherScan .rpcCall (ctx , params , & result ); err != nil {
556563 return nil , err
557564 }
558565 return (* big .Int )(& result ), nil
@@ -581,7 +588,7 @@ func (etherScan *EtherScan) FeeTargets(ctx context.Context) ([]*ethtypes.FeeTarg
581588 params := url.Values {}
582589 params .Set ("module" , "gastracker" )
583590 params .Set ("action" , "gasoracle" )
584- if err := etherScan .call (params , & result ); err != nil {
591+ if err := etherScan .call (ctx , params , & result ); err != nil {
585592 return nil , err
586593 }
587594 // Convert string fields to int64
0 commit comments