Skip to content

Moves utils into trueblocks-core and updates go.mods #3991

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk
Submodule sdk updated 2 files
+7 −7 go.mod
+14 −14 go.sum
12 changes: 6 additions & 6 deletions src/apps/chifra/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ require (
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.10.0
github.com/wealdtech/go-ens/v3 v3.5.2
golang.org/x/crypto v0.35.0
golang.org/x/term v0.29.0
golang.org/x/crypto v0.37.0
golang.org/x/term v0.31.0
golang.org/x/time v0.5.0
)

Expand Down Expand Up @@ -79,10 +79,10 @@ require (
github.com/wealdtech/go-multicodec v1.4.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.1.7 // indirect
Expand Down
24 changes: 12 additions & 12 deletions src/apps/chifra/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -609,8 +609,8 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20220213190939-1e6e3497d506/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand Down Expand Up @@ -667,8 +667,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand All @@ -684,8 +684,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down Expand Up @@ -735,13 +735,13 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
Expand All @@ -750,8 +750,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down
104 changes: 104 additions & 0 deletions src/apps/chifra/pkg/utils/download_and_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package utils

import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"

"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file"
)

// DownloadAndStoreJSON is a generic function that:
// - Downloads from the given URL if the local file is stale.
// - Stores it in the given file path.
// - Unmarshals the JSON bytes into a type T and returns a *T.
//
// T must be a Go type compatible with the JSON structure (e.g. a struct or slice).
func DownloadAndStoreJSON[T any](url, filename string, cacheTTL time.Duration) (*T, error) {
// Use your existing caching logic from "downloadAndStore"
bytes, err := downloadAndStore(url, filename, cacheTTL)
if err != nil {
var zero T
return &zero, err
}

var result T
if err := json.Unmarshal(bytes, &result); err != nil {
return &result, err
}
return &result, nil
}

// downloadAndStore retrieves data from the specified URL and caches it in the provided
// filename for up to `dur`. If the file already exists and is newer than `dur`, it returns
// the file's contents without making a network request. Otherwise, it fetches from the URL.
//
// If the server returns 404, the function writes an empty file to disk and returns a zero-length
// byte slice. For other non-200 status codes, it returns an error.
//
// If the response is valid JSON, it is pretty-formatted before being saved; otherwise it is
// saved as-is. The function returns the written file content as a byte slice.
func downloadAndStore(url, filename string, dur time.Duration) ([]byte, error) {
if file.FileExists(filename) {
lastModDate, err := file.GetModTime(filename)
if err != nil {
return nil, err
}
if time.Since(lastModDate) < dur {
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
return data, nil
}
}

resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode == http.StatusNotFound {
// If the file doesn't exist remotely, store an empty file
if err := os.WriteFile(filename, []byte{}, 0644); err != nil {
return nil, err
}
// Optionally update its mod time
_ = file.Touch(filename)
return []byte{}, nil
} else if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("received status %d %s for URL %s",
resp.StatusCode, resp.Status, url)
}

rawData, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

var prettyData []byte
if json.Valid(rawData) {
var jsonData interface{}
if err := json.Unmarshal(rawData, &jsonData); err != nil {
return nil, err
}
prettyData, err = json.MarshalIndent(jsonData, "", " ")
if err != nil {
return nil, err
}
} else {
prettyData = rawData
}

if err := os.WriteFile(filename, prettyData, 0644); err != nil {
return nil, err
}

_ = file.Touch(filename)

return prettyData, nil
}
74 changes: 74 additions & 0 deletions src/apps/chifra/pkg/utils/get_chain_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package utils

import (
"path/filepath"
"time"

"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file"
)

type ChainList struct {
Chains []ChainListItem `json:"chains"`
ChainsMap map[int]*ChainListItem
}

type ChainListItem struct {
Name string `json:"name"`
Chain string `json:"chain"`
Icon string `json:"icon"`
Rpc []string `json:"rpc"`
Faucets []string `json:"faucets"`
NativeCurrency NativeCurrency `json:"nativeCurrency"`
InfoURL string `json:"infoURL"`
ShortName string `json:"shortName"`
ChainID int `json:"chainId"`
NetworkID int `json:"networkId"`
Explorers []Explorer `json:"explorers"`
}

type NativeCurrency struct {
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
}

type Explorer struct {
Name string `json:"name"`
URL string `json:"url"`
Standard string `json:"standard"`
}

func UpdateChainList(configPath string) (*ChainList, error) {
_ = file.EstablishFolder(configPath)

chainURL := "https://chainid.network/chains.json"
chainsFile := filepath.Join(configPath, "chains.json")

chainData, err := DownloadAndStoreJSON[[]ChainListItem](chainURL, chainsFile, 24*time.Hour)
if err != nil {
return nil, err
}

var chainList ChainList
chainList.Chains = *chainData
chainList.ChainsMap = make(map[int]*ChainListItem)

for _, chain := range chainList.Chains {
chainCopy := chain
chainList.ChainsMap[chain.ChainID] = &chainCopy
}

return &chainList, nil
}

func GetChainListItem(configPath string, chainId int) *ChainListItem {
if chainList, err := UpdateChainList(ResolvePath(configPath)); err != nil {
return nil
} else {
if ch, ok := chainList.ChainsMap[chainId]; !ok {
return nil
} else {
return ch
}
}
}
28 changes: 28 additions & 0 deletions src/apps/chifra/pkg/utils/ping_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package utils

import (
"context"
"net/http"
"time"
)

// PingServer sends a GET request to the provided URL and returns true if
// the server responds with a 200 status code.
func PingServer(serverUrl string) bool {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

req, err := http.NewRequestWithContext(ctx, "GET", serverUrl, nil)
if err != nil {
return false
}

clientHTTP := &http.Client{}
resp, err := clientHTTP.Do(req)
if err != nil {
return false
}
defer resp.Body.Close()

return resp.StatusCode == http.StatusOK
}
20 changes: 20 additions & 0 deletions src/apps/chifra/pkg/utils/remove_any.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package utils

import "strings"

// RemoveAny returns a new string with all characters from string A that are present in
// string B removed. The function uses a map for efficient lookups and preserves the
// order of characters in A.
func RemoveAny(A, B string) string {
result := strings.Builder{}
toRemove := make(map[rune]struct{})
for _, char := range B {
toRemove[char] = struct{}{}
}
for _, char := range A {
if _, exists := toRemove[char]; !exists {
result.WriteRune(char)
}
}
return result.String()
}
34 changes: 34 additions & 0 deletions src/apps/chifra/pkg/utils/remove_any_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package utils

import (
"testing"

"github.com/stretchr/testify/assert"
)

// Testing status: reviewed

func TestRemoveAny(t *testing.T) {
tests := []struct {
name string
A string
B string
expected string
}{
{"Remove characters", "hello world", "lo", "he wrd"},
{"Empty B", "hello", "", "hello"},
{"Empty A", "", "xyz", ""},
{"Remove all", "abc", "abc", ""},
{"No matching characters", "hello", "xyz", "hello"},
{"Unicode characters", "你好世界", "界", "你好世"},
{"Duplicate characters in B", "banana", "na", "b"},
{"Case sensitivity", "Hello", "h", "Hello"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := RemoveAny(tt.A, tt.B)
assert.Equal(t, tt.expected, result)
})
}
}
Loading
Loading