github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/abi/download.go (about) 1 package abi 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "net/http" 9 "os" 10 "strings" 11 12 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" 13 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config" 14 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/debug" 15 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" 16 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/utils" 17 ) 18 19 var perfTiming bool 20 21 func init() { 22 perfTiming = os.Getenv("TB_TIMER_ON") == "true" 23 } 24 25 // DO NOT CHANGE THIS VALUE. It is used to cache the error response from Etherscan. 26 // It's length is 42 bytes and is used to test for this case 27 28 var AbiNotFound = `[{"name":"AbiNotFound","type":"function"}]` 29 30 // downloadAbi downloads the ABI for the given address and saves it to the cache. 31 // TODO: This function should be easy to replace with "ABI providers" (different services like 32 // Sourcify or custom ones configured by the user) 33 func (abiMap *SelectorSyncMap) downloadAbi(chain string, address base.Address) error { 34 if address.IsZero() { 35 return errors.New("address is 0x0 in downloadAbi") 36 } 37 38 // C++ code used do check if the address is contract in 2 places: here and in handle_addresses. We 39 // check only in handle_addresses. 40 41 key := config.GetKey("etherscan").ApiKey 42 if key == "" { 43 return errors.New("cannot read Etherscan API key") 44 } 45 url := fmt.Sprintf( 46 "https://api.etherscan.io/api?module=contract&action=getabi&address=%s&apikey=%s", 47 address.Hex(), 48 key, 49 ) 50 51 debug.DebugCurlStr(url) 52 resp, err := http.Get(url) 53 if err != nil { 54 return err 55 } 56 defer resp.Body.Close() 57 58 // Check server response 59 if resp.StatusCode != http.StatusOK { 60 return fmt.Errorf("etherscan API error: %s", resp.Status) 61 } 62 63 data := map[string]string{} 64 decoder := json.NewDecoder(resp.Body) 65 if err = decoder.Decode(&data); err != nil { 66 return err 67 } 68 resp.Body.Close() 69 70 if data["message"] == "NOTOK" { 71 // Etherscan sends 200 OK responses even if there's an error. We want to cache the error 72 // response so we don't keep asking Etherscan for the same address. The user may later 73 // remove empty ABIs with chifra abis --clean. 74 if !perfTiming && os.Getenv("TEST_MODE") != "true" && !utils.IsFuzzing() { 75 logger.Warn("provider responded with:", address.Hex(), data["message"], ss) 76 } 77 78 reader := strings.NewReader(AbiNotFound) 79 _ = fromJson(reader, abiMap) 80 if _, err = reader.Seek(0, io.SeekStart); err != nil { 81 return err 82 } 83 if err = insertAbi(chain, address, reader); err != nil { 84 return err 85 } 86 return nil 87 } 88 89 reader := strings.NewReader(data["result"]) 90 _ = fromJson(reader, abiMap) 91 if _, err = reader.Seek(0, io.SeekStart); err != nil { 92 return err 93 } 94 95 // Write the body to file 96 if err = insertAbi(chain, address, reader); err != nil { 97 return err 98 } 99 100 return nil 101 } 102 103 var ss = strings.Repeat(" ", 40)