diff --git a/.gitignore b/.gitignore index b2d4cbd..4cd4e3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +main +last.txt +y +newprompts gitcoin-core.txt database.tar.gz save.txt diff --git a/chopper.go b/chopper.go index 989df9a..c6a13aa 100644 --- a/chopper.go +++ b/chopper.go @@ -2,32 +2,15 @@ package main import ( "fmt" - "strings" - - "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" ) var ErrInvalidAddress = fmt.Errorf("not a valid address") var ErrInvalidSeed = fmt.Errorf("invalid seed") func (a *App) Chopper(input string) (string, map[string]string, error) { - if !base.IsValidAddress(input) { - return "", map[string]string{}, ErrInvalidAddress - } - - if strings.HasSuffix(input, ".eth") { - var ok bool - if input, ok = a.conn.GetEnsAddress(input); !ok { - return "", map[string]string{}, ErrInvalidAddress - } - } - - hash := hexutil.Encode(crypto.Keccak256([]byte(input))) - seed := hash[2:] + input[2:] - if len(seed) < 104 { - return "", map[string]string{}, ErrInvalidSeed + _, seed, err := a.SeedBuilder(input) + if err != nil { + return "", map[string]string{}, err } keys := []string{"adverb", "adjective", "emotionshort", "emotion", "literary", "noun", "style", "style2", "color1", "color2", "color3", "variant1", "variant2", "variant3", "background", "orientation"} diff --git a/dalle.go b/dalle.go index 9ea231a..29ff068 100644 --- a/dalle.go +++ b/dalle.go @@ -175,7 +175,8 @@ func (a *App) GetDalledress(ensOrAddr string) (Dalledress, error) { return Dalledress{}, fmt.Errorf("adverbs not loaded") } - addr, _ := a.conn.GetEnsAddress(ensOrAddr) + // addr, _ := a.conn.GetEnsAddress(ensOrAddr) + addr := ensOrAddr // if base.HexToAddress(addr) == base.ZeroAddr || !base.IsValidAddress(addr) { // return Dalledress{}, fmt.Errorf("ENS not registered: %s", ensOrAddr) // } @@ -208,8 +209,8 @@ func (a *App) GetDalledress(ensOrAddr string) (Dalledress, error) { Variant1: Attribute{Seed: seed[56:68]}, Variant2: Attribute{Seed: seed[44:56]}, Variant3: Attribute{Seed: seed[32:44]}, - Background: Attribute{Seed: hash[20:32]}, - Orientation: Attribute{Seed: hash[8:20]}, + Background: Attribute{Seed: seed[20:32]}, + Orientation: Attribute{Seed: seed[8:20]}, } lengths := []int{ @@ -249,8 +250,8 @@ func (a *App) GetDalledress(ensOrAddr string) (Dalledress, error) { dd.Style.Val = a.styles[dd.Style.Num] dd.ShortStyle = a.shortStyles[dd.Style.Num] dd.Style2.Val = a.styles[dd.Style2.Num] - dd.Color1.Val = a.colors[dd.Color1.Num] // "#" + muteColor(dd.Color1.Seed[:8], dd.Color1.Seed[2:2]) - dd.Color2.Val = a.colors[dd.Color1.Num] // "#" + muteColor(dd.Color2.Seed[:8], dd.Color2.Seed[3:3]) + dd.Color1.Val = a.colors[dd.Color1.Num] + dd.Color2.Val = a.colors[dd.Color2.Num] dd.Color3.Val = "#" + muteColor(dd.Color3.Seed[:8], dd.Color3.Seed[4:4]) dd.Variant1.Val = clip(a.styles[dd.Variant1.Num]) dd.Variant2.Val = clip(a.styles[dd.Variant2.Num]) @@ -358,143 +359,143 @@ var reserved = make(map[string]bool) func (a *App) GetImage(ensOrAddr string) { // logger.Info("Generating image for", ensOrAddr) - if addr, _ := a.conn.GetEnsAddress(ensOrAddr); len(addr) < 42 { // base.HexToAddress(addr) == base.ZeroAddr || !base.IsValidAddress(addr) { - logger.Error(fmt.Errorf("ENS not registered: %s", ensOrAddr)) - return - } else { - folder := "./generated/" - file.EstablishFolder(folder) - file.EstablishFolder(strings.Replace(folder, "/generated", "/txt-generated", -1)) - file.EstablishFolder(strings.Replace(folder, "/generated", "/annotated", -1)) - file.EstablishFolder(strings.Replace(folder, "/generated", "/stitched", -1)) - cnt := 0 - fn := "" - for { - fn = filepath.Join(folder, fmt.Sprintf("%s-%05d.png", addr, cnt)) - fM.Lock() - if !file.FileExists(fn) && !reserved[fn] { - reserved[fn] = true - fM.Unlock() - break - } + addr := ensOrAddr + // if addr, _ := a.conn.GetEnsAddress(ensOrAddr); len(addr) < 42 { // base.HexToAddress(addr) == base.ZeroAddr || !base.IsValidAddress(addr) { + // logger.Error(fmt.Errorf("ENS not registered: %s", ensOrAddr)) + // return + // } else { + folder := "./generated/" + file.EstablishFolder(folder) + file.EstablishFolder(strings.Replace(folder, "/generated", "/txt-generated", -1)) + file.EstablishFolder(strings.Replace(folder, "/generated", "/annotated", -1)) + file.EstablishFolder(strings.Replace(folder, "/generated", "/stitched", -1)) + cnt := 0 + fn := "" + for { + fn = filepath.Join(folder, fmt.Sprintf("%s-%05d.png", addr, cnt)) + fM.Lock() + if !file.FileExists(fn) && !reserved[fn] { + reserved[fn] = true fM.Unlock() - cnt++ + break } - // msg := fmt.Sprintf("%s,%s,%s,image\n", utils.FormattedDate(time.Now().Unix()), addr, strings.ToLower(ensOrAddr)) - // file.AppendToAsciiFile("dalledress.csv", msg) - if file.FileExists(fn) { - utils.System("open " + fn) - return - } - - logger.Info(colors.Cyan, addr, colors.Yellow, "- improving the prompt...", colors.Off) + fM.Unlock() + cnt++ + } + // msg := fmt.Sprintf("%s,%s,%s,image\n", utils.FormattedDate(time.Now().Unix()), addr, strings.ToLower(ensOrAddr)) + // file.AppendToAsciiFile("dalledress.csv", msg) + if file.FileExists(fn) { + utils.System("open " + fn) + return + } - prompt := a.GetImprovedPrompt(ensOrAddr) - size := "1024x1024" - if strings.Contains(prompt, "horizontal") { - size = "1792x1024" - } else if strings.Contains(prompt, "vertical") { - size = "1024x1792" - } + logger.Info(colors.Cyan, addr, colors.Yellow, "- improving the prompt...", colors.Off) - quality := "standard" - if os.Getenv("DALLE_QUALITY") != "" { - quality = os.Getenv("DALLE_QUALITY") - } + prompt := a.GetImprovedPrompt(ensOrAddr) + size := "1024x1024" + if strings.Contains(prompt, "horizontal") { + size = "1792x1024" + } else if strings.Contains(prompt, "vertical") { + size = "1024x1792" + } - url := "https://api.openai.com/v1/images/generations" - payload := DalleRequest{ - Prompt: prompt, - N: 1, - Quality: quality, - Style: "vivid", - Model: "dall-e-3", - Size: size, - } + quality := "standard" + if os.Getenv("DALLE_QUALITY") != "" { + quality = os.Getenv("DALLE_QUALITY") + } - payloadBytes, err := json.Marshal(payload) - if err != nil { - panic(err) - } + url := "https://api.openai.com/v1/images/generations" + payload := DalleRequest{ + Prompt: prompt, + N: 1, + Quality: quality, + Style: "vivid", + Model: "dall-e-3", + Size: size, + } - apiKey := os.Getenv("OPENAI_API_KEY") - if apiKey == "" { - log.Fatal("No OPENAI_API_KEY key found") - } + payloadBytes, err := json.Marshal(payload) + if err != nil { + panic(err) + } - logger.Info(colors.Cyan, addr, colors.Yellow, "- generating the image...", colors.Off) + apiKey := os.Getenv("OPENAI_API_KEY") + if apiKey == "" { + log.Fatal("No OPENAI_API_KEY key found") + } - req, err := http.NewRequest("POST", url, bytes.NewBuffer(payloadBytes)) - if err != nil { - panic(err) - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+apiKey) + logger.Info(colors.Cyan, addr, colors.Yellow, "- generating the image...", colors.Off) - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - panic(err) - } - defer resp.Body.Close() + req, err := http.NewRequest("POST", url, bytes.NewBuffer(payloadBytes)) + if err != nil { + panic(err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+apiKey) - body, err := io.ReadAll(resp.Body) - if err != nil { - panic(err) - } + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() - // logger.Info("DalleResponse: ", string(body)) - var dalleResp DalleResponse - err = json.Unmarshal(body, &dalleResp) - if err != nil { - panic(err) - } + body, err := io.ReadAll(resp.Body) + if err != nil { + panic(err) + } - if resp.StatusCode != 200 { - fmt.Println("Error:", resp.Status, resp.StatusCode, string(body)) - return - } + // logger.Info("DalleResponse: ", string(body)) + var dalleResp DalleResponse + err = json.Unmarshal(body, &dalleResp) + if err != nil { + panic(err) + } - if len(dalleResp.Data) == 0 { - fmt.Println("No images returned") - return - } + if resp.StatusCode != 200 { + fmt.Println("Error:", resp.Status, resp.StatusCode, string(body)) + return + } - imageURL := dalleResp.Data[0].Url + if len(dalleResp.Data) == 0 { + fmt.Println("No images returned") + return + } - // Download the image - imageResp, err := http.Get(imageURL) - if err != nil { - panic(err) - } - defer imageResp.Body.Close() + imageURL := dalleResp.Data[0].Url - txtFn := strings.Replace(strings.Replace(fn, ".png", ".txt", -1), "generated", "txt-generated", -1) - file.StringToAsciiFile(txtFn, prompt) - // utils.System("open " + txtFn) + // Download the image + imageResp, err := http.Get(imageURL) + if err != nil { + panic(err) + } + defer imageResp.Body.Close() - os.Remove(fn) - file, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) - if err != nil { - logger.Error("Failed to open output file: ", fn) - panic(err) - } - defer file.Close() + txtFn := strings.Replace(strings.Replace(fn, ".png", ".txt", -1), "generated", "txt-generated", -1) + file.StringToAsciiFile(txtFn, prompt) + // utils.System("open " + txtFn) - _, err = io.Copy(file, imageResp.Body) - if err != nil { - panic(err) - } + os.Remove(fn) + file, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + logger.Error("Failed to open output file: ", fn) + panic(err) + } + defer file.Close() - path, err := annotate(fn, "bottom", 0.2) - if err != nil { - fmt.Println("Error annotating image:", err) - return - } - logger.Info(colors.Cyan, addr, colors.Green, "- image saved as", colors.White+strings.Trim(path, " "), colors.Off) - utils.System("open " + path) + _, err = io.Copy(file, imageResp.Body) + if err != nil { + panic(err) } + path, err := annotate(fn, "bottom", 0.2) + if err != nil { + fmt.Println("Error annotating image:", err) + return + } + logger.Info(colors.Cyan, addr, colors.Green, "- image saved as", colors.White+strings.Trim(path, " "), colors.Off) + utils.System("open " + path) + // } } func (a *App) GetModeration(ensOrAddr string) string { diff --git a/images.go b/images.go index ead4d4c..d519702 100644 --- a/images.go +++ b/images.go @@ -28,22 +28,22 @@ func loadImageAsBase64(path string) (string, error) { } func (a *App) GetImageData(ensOrAddr string) string { - if addr, _ := a.conn.GetEnsAddress(ensOrAddr); len(addr) < 42 { // base.HexToAddress(addr) == base.ZeroAddr || !base.IsValidAddress(addr) { - logger.Error(fmt.Errorf("ENS not registered: %s", ensOrAddr)) - return "" - } else { - folder := "./generated/" - fn := filepath.Join(folder, fmt.Sprintf("%s.png", addr)) - if file.FileExists(fn) { - - base64Image, err := loadImageAsBase64("path/to/your/image.png") - if err != nil { - return "" - } - return "data:image/png;base64," + base64Image - } else { - logger.Fatal(fn + " not found") + addr := ensOrAddr + // if addr, _ := a.conn.GetEnsAddress(ensOrAddr); len(addr) < 42 { // base.HexToAddress(addr) == base.ZeroAddr || !base.IsValidAddress(addr) { + // logger.Error(fmt.Errorf("ENS not registered: %s", ensOrAddr)) + // return "" + // } else { + folder := "./generated/" + fn := filepath.Join(folder, fmt.Sprintf("%s.png", addr)) + if file.FileExists(fn) { + base64Image, err := loadImageAsBase64("path/to/your/image.png") + if err != nil { + return "" } + return "data:image/png;base64," + base64Image + } else { + logger.Fatal(fn + " not found") } + // } return "" } diff --git a/main.go b/main.go index c9f67b1..f442747 100644 --- a/main.go +++ b/main.go @@ -1,33 +1,247 @@ +// package main + +// import "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" + +// func main() { +// i := Images +// // i := Annotate // Stitch +// // i := Stitch +// // i := Wails + +// switch i { +// case Images: +// main_images() +// case Annotate: +// main_annotate() +// case Stitch: +// main_stitch() +// case Wails: +// main_wails() +// default: +// logger.Panic("Invalid mode") +// } +// } + +// type Mode int + +// const ( +// Unused Mode = iota +// Images +// Annotate +// Stitch +// Wails +// ) + package main -import "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" +import ( + "encoding/json" + "fmt" + "os" + "sync" + "time" -func main() { - i := Images - // i := Annotate // Stitch - // i := Stitch - // i := Wails - - switch i { - case Images: - main_images() - case Annotate: - main_annotate() - case Stitch: - main_stitch() - case Wails: - main_wails() - default: - logger.Panic("Invalid mode") - } -} - -type Mode int - -const ( - Unused Mode = iota - Images - Annotate - Stitch - Wails + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/colors" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" ) + +type Database struct { + FileName string + Rows []string +} + +type App1 struct { + Databases map[string]Database + conn *rpc.Connection +} + +type DalleDress struct { + Orig string `json:"orig"` + Seed string `json:"seed"` + Parts []string `json:"parts"` + Prompt string `json:"prompt"` + Enhanced string `json:"enhanced"` + ImageFilename string `json:"imageFilename"` +} + +func (d DalleDress) String() string { + bytes, _ := json.MarshalIndent(d, "", " ") + return string(bytes) +} + +func main() { + var wg sync.WaitGroup + file.EstablishFolder("newprompts") + validateChan := make(chan string) + seederChan := make(chan DalleDress) + chopperChan := make(chan DalleDress) + builderChan := make(chan DalleDress) + promptChan := make(chan DalleDress) + enhancedChan := make(chan DalleDress) + imageChan := make(chan DalleDress) + + a := App1{} + if a.conn = rpc.NewConnection("mainnet", true, map[string]bool{ + "blocks": true, + "receipts": true, + "transactions": true, + "traces": true, + "logs": true, + "statements": true, + "state": true, + "tokens": true, + "results": true, + }); a.conn == nil { + logger.Error("Could not find rpc server.") + } + + go a.validateInput(validateChan, seederChan) + go a.seeder(seederChan, chopperChan) + go a.chopper(chopperChan, builderChan) + go a.prompter(builderChan, promptChan) + go a.enhancer(promptChan, enhancedChan) + go a.generator(enhancedChan, imageChan, &wg) + + for bn := uint64(0); bn < 19300000; bn += 19300000 / 10000 { + fmt.Printf("chifra blocks --hashes %d --fmt json\n", bn) + // if block, err := a.conn.GetBlockBodyByNumber(bn); err != nil { + // logger.Error(err) + // } else { + // fmt.Println(block.Hash.Hex()) + // wg.Add(1) + // go func(ln string) { + // validateChan <- ln + // }(block.Hash.Hex()) + // } + } + // lines := file.AsciiFileToLines("addresses.txt") + // for _, line := range lines { + // wg.Add(1) + // go func(ln string) { + // validateChan <- ln + // }(line) + // } + + go func() { + wg.Wait() + close(imageChan) + }() + + cnt := 1 + for image := range imageChan { + a.LogComplete(image.ImageFilename) + if cnt%5 == 0 { + time.Sleep(250 * time.Millisecond) // to let the other channels clear + a.LogSleeper(sleepBetween) + time.Sleep(sleepBetween) + } + cnt++ + } +} + +func (a *App1) validateInput(input chan string, output chan DalleDress) { + for orig := range input { + if isValid(orig) { + a.LogProgress("validateInput", orig) + output <- DalleDress{Orig: orig} + } else { + fmt.Println("Invalid input") + // Properly handle invalid input without halting the entire process + } + } + close(output) +} + +func isValid(input string) bool { + // Assume implementation of isValid exists + return true +} + +func (a *App1) seeder(input chan DalleDress, output chan DalleDress) { + for dress := range input { + hash := hexutil.Encode(crypto.Keccak256([]byte(dress.Orig))) + dress.Seed = hash[2:] + dress.Orig[2:] + a.LogProgress("seeder", dress.Seed) + output <- dress + } + close(output) +} + +func (a *App1) chopper(input chan DalleDress, output chan DalleDress) { + for dress := range input { + dress.Parts = []string{dress.Seed[:4], dress.Seed[4:8], dress.Seed[8:12]} + a.LogProgress("chopper", dress.Parts) + output <- dress + } + close(output) +} + +func (a *App1) prompter(input chan DalleDress, output chan DalleDress) { + for dress := range input { + dress.Prompt = fmt.Sprintf("A dress with colors %s, pattern %s, and style %s", dress.Parts[0], dress.Parts[1], dress.Parts[2]) + a.LogProgress("prompter", dress.Prompt) + output <- dress + } + close(output) +} + +func (a *App1) enhancer(input chan DalleDress, output chan DalleDress) { + for dress := range input { + dress.Enhanced = dress.Prompt + " in a beautiful outdoor setting" + a.LogProgress("enhancer", dress.Enhanced) + output <- dress + } + close(output) +} + +var sleepBetween = (time.Second * 5) + +func (a *App1) generator(input chan DalleDress, output chan DalleDress, wg *sync.WaitGroup) { + // cnt := 1 + for dress := range input { + filename := fmt.Sprintf("newprompts/%s.txt", dress.Orig) + file, err := os.Create(filename) + if err != nil { + fmt.Println("Error creating file:", err) + dress.ImageFilename = "Error creating file" + } else { + file.WriteString(dress.Enhanced) + file.Close() + dress.ImageFilename = filename + } + output <- dress + wg.Done() + // if cnt%5 == 0 { + // a.LogSleeper(sleepBetween) + // time.Sleep(sleepBetween) + // } + // cnt++ + } +} + +var m sync.Mutex + +func (a *App1) LogProgress(stage string, message interface{}) { + m.Lock() + defer m.Unlock() + logger.Info(colors.Green+"Progress:", stage, message, colors.Off) +} + +var nImages = 0 + +func (a *App1) LogComplete(message string) { + m.Lock() + defer m.Unlock() + nImages++ + logger.Info(fmt.Sprintf(colors.Cyan+"Completed[%d]: %s", nImages, message+colors.Off)) +} + +func (a *App1) LogSleeper(duration time.Duration) { + m.Lock() + defer m.Unlock() + logger.Info(colors.Yellow+"Sleeping for", duration, "seconds", colors.Off) +} diff --git a/main_images.go b/main_images.go index 5fc0242..4154f03 100644 --- a/main_images.go +++ b/main_images.go @@ -4,11 +4,13 @@ import ( "context" "fmt" "os" + "strings" "sync" "time" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file" "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/utils" ) func main_images() { @@ -20,13 +22,24 @@ func main_images() { lines := file.AsciiFileToLines("addresses.txt") if len(lines) > 0 { wg := sync.WaitGroup{} + fn := "/Users/jrush/Development/trueblocks-dalledress/last.txt" + v := strings.Trim(file.AsciiFileToString(fn), "\n\r") + last := utils.MustParseInt(v) + logger.Info("Starting at address ", last, " of ", len(lines), file.FileExists(fn), v) for i := 0; i < len(lines); i++ { - wg.Add(1) - go doOne(&wg, app, lines[i]) - if (i+1)%5 == 0 { - wg.Wait() - logger.Info("Sleeping for 60 seconds") - time.Sleep(time.Second * 60) + if i > int(last) { + if address, ok := app.validateInput(lines[i]); !ok { + return + } else { + wg.Add(1) + go doOne(&wg, app, address.Hex()) + file.StringToAsciiFile(fn, fmt.Sprintf("%d\n", i)) + if (i+1)%5 == 0 { + wg.Wait() + logger.Info("Sleeping for 60 seconds") + time.Sleep(time.Second * 60) + } + } } } wg.Wait() diff --git a/makefile b/makefile new file mode 100644 index 0000000..1b3fa8b --- /dev/null +++ b/makefile @@ -0,0 +1,6 @@ +all: + go build main.go + +all2: + go build app.go chopper.go dalle.go images.go main.go main_annotate.go main_images.go main_stitch.go main_test.go main_wails.go openai.go seeder.go validate.go + diff --git a/seeder.go b/seeder.go new file mode 100644 index 0000000..b9307be --- /dev/null +++ b/seeder.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" +) + +var ErrCouldNotResolveEns = fmt.Errorf("could not resolve ENS address") +var ErrNotAHexString = fmt.Errorf("input is not a valid hex string") + +func (a *App) SeedBuilder(input string) (string, string, error) { + if strings.HasSuffix(input, ".eth") { + var ok bool + if input, ok = a.conn.GetEnsAddress(input); !ok { + return input, "", ErrCouldNotResolveEns + } + } + + isHexValid := func(s string) bool { + trimmedString := strings.TrimPrefix(s, "0x") + _, err := strconv.ParseInt(trimmedString, 16, 0) + return err == nil + } + if !isHexValid(input) { + return "", "", ErrNotAHexString + } + + hash := hexutil.Encode(crypto.Keccak256([]byte(input))) + if len(hash) < 2 || len(input) < 2 { + return "", "", ErrInvalidSeed + } + + seed := hash[2:] + input[2:] + if len(seed) < 104 { + return "", "", ErrInvalidSeed + } + + return "0x" + input, "0x" + hash + input, nil +} diff --git a/validate.go b/validate.go new file mode 100644 index 0000000..caedee3 --- /dev/null +++ b/validate.go @@ -0,0 +1,20 @@ +package main + +import ( + "strings" + + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" +) + +func (a *App) validateInput(s string) (base.Address, bool) { + if strings.HasSuffix(s, ".eth") { + if a.conn != nil { + if addr, ok := a.conn.GetEnsAddress(s); ok { + return base.HexToAddress(addr), true + } + return base.ZeroAddr, false + } + } + + return base.HexToAddress(s), base.IsValidAddress(s) +}