feat: load token list in code
This commit is contained in:
parent
51e3d800bb
commit
2375b10b03
|
@ -0,0 +1,117 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-go/services/wallet/token"
|
||||
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
const uniswapTokensURL = "https://gateway.ipfs.io/ipns/tokens.uniswap.org" // nolint:gosec
|
||||
const tokenListSchemaURL = "https://uniswap.org/tokenlist.schema.json" // nolint:gosec
|
||||
|
||||
const templateText = `
|
||||
package token
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
var uniswapTokens = []*Token{
|
||||
{{ range $token := .Tokens }}
|
||||
{
|
||||
Address: common.HexToAddress("{{ $token.Address }}"),
|
||||
Name: "{{ $token.Name }}",
|
||||
Symbol: "{{ $token.Symbol }}",
|
||||
Color: "{{ $token.Color }}",
|
||||
Decimals: {{ $token.Decimals }},
|
||||
ChainID: {{ $token.ChainID }},
|
||||
PegSymbol: "{{ $token.PegSymbol }}",
|
||||
},
|
||||
{{ end }}
|
||||
}
|
||||
`
|
||||
|
||||
func validateDocument(doc string, schemaURL string) (bool, error) {
|
||||
schemaLoader := gojsonschema.NewReferenceLoader(schemaURL)
|
||||
docLoader := gojsonschema.NewStringLoader(doc)
|
||||
|
||||
result, err := gojsonschema.Validate(schemaLoader, docLoader)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !result.Valid() {
|
||||
return false, errors.New("Token list does not match schema")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func bytesToTokens(tokenListData []byte) ([]*token.Token, error) {
|
||||
var objmap map[string]json.RawMessage
|
||||
err := json.Unmarshal(tokenListData, &objmap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tokens []*token.Token
|
||||
err = json.Unmarshal(objmap["tokens"], &tokens)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
client := &http.Client{Timeout: time.Minute}
|
||||
response, err := client.Get(uniswapTokensURL)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to fetch tokens: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to read tokens: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = validateDocument(string(body), tokenListSchemaURL)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to validate token list against schema: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
tokens, err := bytesToTokens(body)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to parse token list: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := template.Must(template.New("tokens").Parse(templateText))
|
||||
|
||||
// Create the output Go file
|
||||
file, err := os.Create("uniswap.go")
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create go file: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Execute the template with the tokens data and write the result to the file
|
||||
err = tmpl.Execute(file, struct{ Tokens []*token.Token }{Tokens: tokens})
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to write file: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package token
|
||||
|
||||
//go:generate go run ./downloader/main.go
|
|
@ -47,12 +47,13 @@ func (t *Token) IsNative() bool {
|
|||
|
||||
// Manager is used for accessing token store. It changes the token store based on overridden tokens
|
||||
type Manager struct {
|
||||
db *sql.DB
|
||||
RPCClient *rpc.Client
|
||||
networkManager *network.Manager
|
||||
stores []store
|
||||
tokenList []*Token
|
||||
tokenMap storeMap
|
||||
db *sql.DB
|
||||
RPCClient *rpc.Client
|
||||
networkManager *network.Manager
|
||||
stores []store
|
||||
tokenList []*Token
|
||||
tokenMap storeMap
|
||||
areTokensFetched bool
|
||||
}
|
||||
|
||||
func NewTokenManager(
|
||||
|
@ -61,8 +62,7 @@ func NewTokenManager(
|
|||
networkManager *network.Manager,
|
||||
) *Manager {
|
||||
// Order of stores is important when merging token lists. The former prevale
|
||||
tokenManager := &Manager{db, RPCClient, networkManager, []store{newUniswapStore(), newDefaultStore()}, nil, nil}
|
||||
|
||||
tokenManager := &Manager{db, RPCClient, networkManager, []store{newUniswapStore(), newDefaultStore()}, nil, nil, false}
|
||||
return tokenManager
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,7 @@ func (tm *Manager) inStore(address common.Address, chainID uint64) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
if !tm.areTokensFetched() {
|
||||
if !tm.areTokensFetched {
|
||||
tm.fetchTokens()
|
||||
}
|
||||
|
||||
|
@ -117,16 +117,6 @@ func (tm *Manager) inStore(address common.Address, chainID uint64) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
func (tm *Manager) areTokensFetched() bool {
|
||||
for _, store := range tm.stores {
|
||||
if !store.areTokensFetched() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (tm *Manager) fetchTokens() {
|
||||
tm.tokenList = nil
|
||||
tm.tokenMap = nil
|
||||
|
@ -137,11 +127,7 @@ func (tm *Manager) fetchTokens() {
|
|||
}
|
||||
|
||||
for _, store := range tm.stores {
|
||||
tokens, err := store.GetTokens()
|
||||
if err != nil {
|
||||
log.Error("can't fetch tokens from store", "error", err)
|
||||
continue
|
||||
}
|
||||
tokens := store.GetTokens()
|
||||
validTokens := make([]*Token, 0)
|
||||
for _, token := range tokens {
|
||||
for _, network := range networks {
|
||||
|
@ -154,7 +140,7 @@ func (tm *Manager) fetchTokens() {
|
|||
|
||||
tm.tokenList = mergeTokenLists([][]*Token{tm.tokenList, validTokens})
|
||||
}
|
||||
|
||||
tm.areTokensFetched = true
|
||||
tm.tokenMap = toTokenMap(tm.tokenList)
|
||||
}
|
||||
|
||||
|
@ -230,7 +216,7 @@ func (tm *Manager) GetAllTokensAndNativeCurrencies() ([]*Token, error) {
|
|||
}
|
||||
|
||||
func (tm *Manager) GetAllTokens() ([]*Token, error) {
|
||||
if !tm.areTokensFetched() {
|
||||
if !tm.areTokensFetched {
|
||||
tm.fetchTokens()
|
||||
}
|
||||
|
||||
|
@ -259,7 +245,7 @@ func (tm *Manager) GetTokensByChainIDs(chainIDs []uint64) ([]*Token, error) {
|
|||
}
|
||||
|
||||
func (tm *Manager) GetTokens(chainID uint64) ([]*Token, error) {
|
||||
if !tm.areTokensFetched() {
|
||||
if !tm.areTokensFetched {
|
||||
tm.fetchTokens()
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
func setupTestTokenDB(t *testing.T) (*Manager, func()) {
|
||||
db, err := appdatabase.InitializeDB(":memory:", "wallet-token-tests", 1)
|
||||
require.NoError(t, err)
|
||||
return &Manager{db, nil, nil, nil, nil, nil}, func() {
|
||||
return &Manager{db, nil, nil, nil, nil, nil, false}, func() {
|
||||
require.NoError(t, db.Close())
|
||||
}
|
||||
}
|
||||
|
@ -100,12 +100,10 @@ func TestTokenOverride(t *testing.T) {
|
|||
}
|
||||
testStore := &DefaultStore{
|
||||
tokenList,
|
||||
false,
|
||||
}
|
||||
|
||||
overrideTokensInPlace(networks, testStore.tokenList)
|
||||
tokens, err := testStore.GetTokens()
|
||||
require.NoError(t, err)
|
||||
tokens := testStore.GetTokens()
|
||||
tokenMap := toTokenMap(tokens)
|
||||
_, found := tokenMap[1][common.Address{1}]
|
||||
require.False(t, found)
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
package token
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
|
@ -13,8 +8,7 @@ type addressTokenMap = map[common.Address]*Token
|
|||
type storeMap = map[uint64]addressTokenMap
|
||||
|
||||
type store interface {
|
||||
GetTokens() ([]*Token, error)
|
||||
areTokensFetched() bool
|
||||
GetTokens() []*Token
|
||||
}
|
||||
|
||||
var tokenPeg = map[string]string{
|
||||
|
@ -38,38 +32,6 @@ func GetTokenPegSymbol(symbol string) string {
|
|||
return tokenPeg[symbol]
|
||||
}
|
||||
|
||||
func validateDocument(doc string, schemaURL string) (bool, error) {
|
||||
schemaLoader := gojsonschema.NewReferenceLoader(schemaURL)
|
||||
docLoader := gojsonschema.NewStringLoader(doc)
|
||||
|
||||
result, err := gojsonschema.Validate(schemaLoader, docLoader)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !result.Valid() {
|
||||
return false, errors.New("Token list does not match schema")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func bytesToTokens(tokenListData []byte) ([]*Token, error) {
|
||||
var objmap map[string]json.RawMessage
|
||||
err := json.Unmarshal(tokenListData, &objmap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tokens []*Token
|
||||
err = json.Unmarshal(objmap["tokens"], &tokens)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
func toTokenMap(tokens []*Token) storeMap {
|
||||
tokenMap := storeMap{}
|
||||
|
||||
|
@ -87,8 +49,7 @@ func toTokenMap(tokens []*Token) storeMap {
|
|||
}
|
||||
|
||||
type DefaultStore struct {
|
||||
tokenList []*Token
|
||||
tokensFetched bool
|
||||
tokenList []*Token
|
||||
}
|
||||
|
||||
func newDefaultStore() *DefaultStore {
|
||||
|
@ -1711,15 +1672,9 @@ func newDefaultStore() *DefaultStore {
|
|||
ChainID: 421613,
|
||||
},
|
||||
},
|
||||
tokensFetched: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *DefaultStore) GetTokens() ([]*Token, error) {
|
||||
ts.tokensFetched = true
|
||||
return ts.tokenList, nil
|
||||
}
|
||||
|
||||
func (ts *DefaultStore) areTokensFetched() bool {
|
||||
return ts.tokensFetched
|
||||
func (ts *DefaultStore) GetTokens() []*Token {
|
||||
return ts.tokenList
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,54 +1,12 @@
|
|||
package token
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type uniswapStore struct {
|
||||
client *http.Client
|
||||
tokensFetched bool
|
||||
}
|
||||
|
||||
const uniswapTokensURL = "https://gateway.ipfs.io/ipns/tokens.uniswap.org" // nolint:gosec
|
||||
const tokenListSchemaURL = "https://uniswap.org/tokenlist.schema.json" // nolint:gosec
|
||||
|
||||
func newUniswapStore() *uniswapStore {
|
||||
return &uniswapStore{client: &http.Client{Timeout: time.Minute}, tokensFetched: false}
|
||||
return &uniswapStore{}
|
||||
}
|
||||
|
||||
func (ts *uniswapStore) doQuery(url string) (*http.Response, error) {
|
||||
return ts.client.Get(url)
|
||||
}
|
||||
|
||||
func (ts *uniswapStore) areTokensFetched() bool {
|
||||
return ts.tokensFetched
|
||||
}
|
||||
|
||||
func (ts *uniswapStore) GetTokens() ([]*Token, error) {
|
||||
resp, err := ts.doQuery(uniswapTokensURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// In an unlikely case when token list is fetched fine,
|
||||
// but fails to validate against the schema, we don't want
|
||||
// to refetch the tokens on every GetTokens call as it will
|
||||
// still fail but will be wasting CPU cycles until restart,
|
||||
// so lets keep tokensFetched before validate() call
|
||||
ts.tokensFetched = true
|
||||
|
||||
_, err = validateDocument(string(body), tokenListSchemaURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytesToTokens(body)
|
||||
func (ts *uniswapStore) GetTokens() []*Token {
|
||||
return uniswapTokens
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue