diff --git a/v2/.DS_Store b/v2/.DS_Store new file mode 100644 index 0000000..c73ef06 Binary files /dev/null and b/v2/.DS_Store differ diff --git a/v2/cmd/price_filler/main.go b/v2/cmd/price_filler/main.go index df1e232..90a0964 100644 --- a/v2/cmd/price_filler/main.go +++ b/v2/cmd/price_filler/main.go @@ -5,9 +5,10 @@ import ( "log" "os" - "github.com/KyberNetwork/go-binance/v2" libapp "github.com/KyberNetwork/tradelogs/v2/pkg/app" - "github.com/KyberNetwork/tradelogs/v2/pkg/price_filler" + "github.com/KyberNetwork/tradelogs/v2/pkg/mtm" + pricefiller "github.com/KyberNetwork/tradelogs/v2/pkg/price_filler" + dashboardStorage "github.com/KyberNetwork/tradelogs/v2/pkg/storage/dashboard" bebopStorage "github.com/KyberNetwork/tradelogs/v2/pkg/storage/tradelogs/bebop" hashflowv3Storage "github.com/KyberNetwork/tradelogs/v2/pkg/storage/tradelogs/hashflow_v3" kyberswapStorage "github.com/KyberNetwork/tradelogs/v2/pkg/storage/tradelogs/kyberswap" @@ -21,11 +22,15 @@ import ( zxrfqv3Storage "github.com/KyberNetwork/tradelogs/v2/pkg/storage/tradelogs/zxrfqv3" "github.com/KyberNetwork/tradinglib/pkg/dbutil" "github.com/jmoiron/sqlx" + "github.com/joho/godotenv" "github.com/urfave/cli" "go.uber.org/zap" ) func main() { + if err := godotenv.Load("tradelogs.env"); err != nil { + log.Println("load env error", err) + } app := libapp.NewApp() app.Name = "trade logs crawler service" app.Action = run @@ -68,9 +73,9 @@ func run(c *cli.Context) error { zxrfqv3Storage.New(l, db), pancakeswapStorage.New(l, db), } - - binanceClient := binance.NewClient(c.String(libapp.BinanceAPIKeyFlag.Name), c.String(libapp.BinanceSecretKeyFlag.Name)) - priceFiller, err := pricefiller.NewPriceFiller(l, binanceClient, s) + mtmClient := mtm.NewMtmClient(c.String(libapp.MarkToMarketURLFlag.Name)) + dashboardStorage := dashboardStorage.New(l, db) + priceFiller, err := pricefiller.NewPriceFiller(l, s, mtmClient, dashboardStorage) if err != nil { l.Errorw("Error while init price filler") return err diff --git a/v2/internal/server/tradelogs.go b/v2/internal/server/tradelogs.go index 1ee936b..1f1d22c 100644 --- a/v2/internal/server/tradelogs.go +++ b/v2/internal/server/tradelogs.go @@ -5,6 +5,8 @@ import ( "net/http" "time" + dashboardStorage "github.com/KyberNetwork/tradelogs/v2/pkg/storage/dashboard" + dashboardTypes "github.com/KyberNetwork/tradelogs/v2/pkg/storage/dashboard/types" storageTypes "github.com/KyberNetwork/tradelogs/v2/pkg/storage/tradelogs/types" "github.com/gin-contrib/pprof" "github.com/gin-gonic/gin" @@ -16,10 +18,11 @@ var ( ) type TradeLogs struct { - r *gin.Engine - bindAddr string - l *zap.SugaredLogger - storage []storageTypes.Storage + r *gin.Engine + bindAddr string + l *zap.SugaredLogger + storage []storageTypes.Storage + dashStorage *dashboardStorage.Storage } func NewTradeLogs(l *zap.SugaredLogger, s []storageTypes.Storage, bindAddr string) *TradeLogs { @@ -51,6 +54,9 @@ func (s *TradeLogs) Run() error { func (s *TradeLogs) register() { pprof.Register(s.r, "/debug") s.r.GET("/tradelogs", s.getTradeLogs) + s.r.GET("/tokens", s.getTokens) + // s.r.GET("/contracts", s.getContracts) + // s.r.POST("/add_contracts", s.addContracts) } func (s *TradeLogs) getTradeLogs(c *gin.Context) { @@ -91,3 +97,52 @@ func (s *TradeLogs) getTradeLogs(c *gin.Context) { "data": data, }) } + +func (s *TradeLogs) getTokens(c *gin.Context) { + var queries dashboardTypes.TokenQuery + if err := c.ShouldBindJSON(&queries); err != nil { + responseErr(c, http.StatusBadRequest, err) + return + } + + data, err := s.dashStorage.GetTokens(queries) + if err != nil { + responseErr(c, http.StatusBadRequest, err) + return + } + c.JSON(http.StatusOK, gin.H{ + "success": true, + "data": data, + }) +} + +// func (s *TradeLogs) getContracts(c *gin.Context) { +// data, err := s.dashStorage.GetContracts() +// if err != nil { +// responseErr(c, http.StatusBadRequest, err) +// return +// } +// c.JSON(http.StatusOK, gin.H{ +// "success": true, +// "data": data, +// }) +// } + +// func (s *TradeLogs) addContracts(c *gin.Context) { +// var queries []dashboardTypes.Contract + +// if err := c.ShouldBindJSON(&queries); err != nil { +// responseErr(c, http.StatusBadRequest, err) +// return +// } + +// if err := s.dashStorage.InsertContract(queries); err != nil { +// responseErr(c, http.StatusInternalServerError, err) +// return +// } + +// c.JSON(http.StatusOK, gin.H{ +// "success": true, +// "data": queries, +// }) +// } diff --git a/v2/pkg/app/price_filler.go b/v2/pkg/app/price_filler.go index 9027785..a5caadf 100644 --- a/v2/pkg/app/price_filler.go +++ b/v2/pkg/app/price_filler.go @@ -13,10 +13,15 @@ var BinanceSecretKeyFlag = cli.StringFlag{ Name: "binance-secret-key", EnvVar: "BINANCE_SECRET_KEY", } +var MarkToMarketURLFlag = cli.StringFlag{ + Name: "mark-to-market-url", + EnvVar: "MARK_TO_MARKET_URL", +} func PriceFillerFlags() []cli.Flag { return []cli.Flag{ BinanceAPIKeyFlag, BinanceSecretKeyFlag, + MarkToMarketURLFlag, } } diff --git a/v2/pkg/mtm/client.go b/v2/pkg/mtm/client.go new file mode 100644 index 0000000..41a5b3d --- /dev/null +++ b/v2/pkg/mtm/client.go @@ -0,0 +1,116 @@ +package mtm + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + tokenStorage "github.com/KyberNetwork/tradelogs/v2/pkg/storage/dashboard/types" +) + +type MtmClient struct { + host string +} + +func NewMtmClient(host string) *MtmClient { + return &MtmClient{ + host: host, + } +} + +func (m *MtmClient) GetListTokens(ctx context.Context) ([]tokenStorage.Token, error) { + const path = "/tokens" + req, err := http.NewRequestWithContext(ctx, http.MethodGet, m.host+path, nil) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + req.Header.Set("Accept", "application/json") + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + var tokens TokenResponse + + if err := json.NewDecoder(resp.Body).Decode(&tokens); err != nil { + return nil, fmt.Errorf("failed to decode JSON: %w", err) + } + + return tokens.Data, nil +} + +func (m *MtmClient) GetHistoricalRate( + ctx context.Context, + base, quote, chainId string, + timestamp int64, +) (float64, error) { + const path = "/v3/historical" + queryParam := fmt.Sprintf("?base=%s"e=%s&time=%d&chainid=%s", + base, + quote, + timestamp, + chainId, + ) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, m.host+path+queryParam, nil) + if err != nil { + return 0, fmt.Errorf("unable to create request: %w", err) + } + req.Header.Set("Accept", "application/json") + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return 0, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + var rate RateResponse + + if err := json.NewDecoder(resp.Body).Decode(&rate); err != nil { + return 0, fmt.Errorf("failed to decode JSON: %w", err) + } + + return rate.Data.Price, nil +} + +func (m *MtmClient) GetCurrentRate(ctx context.Context, base, quote, chainId string) (float64, error) { + const path = "/v3/rate" + queryParam := fmt.Sprintf("?base=%s"e=%s&chainid=%s", + base, + quote, + chainId, + ) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, m.host+path+queryParam, nil) + if err != nil { + return 0, fmt.Errorf("unable to create request: %w", err) + } + req.Header.Set("Accept", "application/json") + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return 0, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + var rate RateResponse + + if err := json.NewDecoder(resp.Body).Decode(&rate); err != nil { + return 0, fmt.Errorf("failed to decode JSON: %w", err) + } + + return rate.Data.Price, nil +} + +type RateResponse struct { + Success bool `json:"success"` + Data struct { + Price float64 `json:"price"` + TimeUnix int64 `json:"timeUnix"` + } `json:"data"` +} + +type TokenResponse struct { + Success bool `json:"success"` + Data []tokenStorage.Token `json:"data"` +} diff --git a/v2/pkg/mtm/client_test.go b/v2/pkg/mtm/client_test.go new file mode 100644 index 0000000..4243052 --- /dev/null +++ b/v2/pkg/mtm/client_test.go @@ -0,0 +1,28 @@ +package mtm + +import ( + "context" + "fmt" + "testing" + + "github.com/test-go/testify/require" +) + +func TestNewMtmClient(t *testing.T) { + // need mtm url + t.Skip() + MTM_URL := "" + client := NewMtmClient(MTM_URL) + + rate, err := client.GetCurrentRate(context.Background(), "0x9be89d2a4cd102d8fecc6bf9da793be995c22541", "0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7", "1") + require.NoError(t, err) + fmt.Println("rate", rate) + + rate, err = client.GetHistoricalRate(context.Background(), "0x9be89d2a4cd102d8fecc6bf9da793be995c22541", "0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7", "1", 1732608687) + require.NoError(t, err) + fmt.Println("historical rate", rate) + + tokens, err := client.GetListTokens(context.Background()) + require.NoError(t, err) + fmt.Println("tokens", tokens) +} diff --git a/v2/pkg/price_filler/ks_client.go b/v2/pkg/price_filler/ks_client.go index 38dba3c..080e408 100644 --- a/v2/pkg/price_filler/ks_client.go +++ b/v2/pkg/price_filler/ks_client.go @@ -85,7 +85,9 @@ type TokenCatalogResp struct { } type TokenCatalog struct { - Decimals int64 `json:"decimals"` + Decimals int64 `json:"decimals"` + Symbol string `json:"symbol"` + ChainId int16 `json:"chainId"` } func (c *KsClient) GetTokenCatalog(address string) (TokenCatalogResp, error) { @@ -113,12 +115,19 @@ type ImportTokenParam struct { Tokens []ImportedToken `json:"tokens"` } +type TokenCatalogImportResp struct { + Decimals int64 `json:"decimals"` + ChainId string `json:"chainId"` + Address string `json:"address"` + Symbol string `json:"symbol"` +} + type ImportTokenResp struct { Code int64 `json:"code"` Message string `json:"message"` Data struct { Tokens []struct { - Data TokenCatalog `json:"data"` + Data TokenCatalogImportResp `json:"data"` } `json:"tokens"` } `json:"data"` } diff --git a/v2/pkg/price_filler/price_filler.go b/v2/pkg/price_filler/price_filler.go index e406975..16511da 100644 --- a/v2/pkg/price_filler/price_filler.go +++ b/v2/pkg/price_filler/price_filler.go @@ -3,13 +3,14 @@ package pricefiller import ( "context" "errors" - "github.com/KyberNetwork/tradelogs/v2/pkg/constant" - "strconv" "strings" "sync" "time" - "github.com/KyberNetwork/go-binance/v2" + "github.com/KyberNetwork/tradelogs/v2/pkg/constant" + "github.com/KyberNetwork/tradelogs/v2/pkg/mtm" + dashboardStorage "github.com/KyberNetwork/tradelogs/v2/pkg/storage/dashboard" + dashboardTypes "github.com/KyberNetwork/tradelogs/v2/pkg/storage/dashboard/types" storageTypes "github.com/KyberNetwork/tradelogs/v2/pkg/storage/tradelogs/types" "go.uber.org/zap" ) @@ -24,56 +25,57 @@ const ( addressETH1 = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" addressETH2 = "0x0000000000000000000000000000000000000000" coinUSDT = "USDT" + USDTAddress = "0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7" invalidSymbolErrString = " code=-1121, msg=Invalid symbol." ) var ( ErrNoPrice = errors.New("no price from binance") ErrWeirdTokenCatalogResp = errors.New("weird token catalog response") - - mappedMultiplier = map[string]float64{ - "1MBABYDOGE": 1e-6, - "1000SATS": 1e-3, - } ) type CoinInfo struct { Coin string - Network string + ChainId int64 ContractAddress string Decimals int64 } type PriceFiller struct { - l *zap.SugaredLogger - s []storageTypes.Storage - mu sync.Mutex - ksClient *KsClient - binanceClient *binance.Client - mappedCoinInfo map[string]CoinInfo // address - coinInfo + l *zap.SugaredLogger + s []storageTypes.Storage + mu sync.Mutex + ksClient *KsClient + mappedCoinInfo map[string]CoinInfo // address - coinInfo + mtmClient *mtm.MtmClient + dashboardStorage *dashboardStorage.Storage } -func NewPriceFiller(l *zap.SugaredLogger, binanceClient *binance.Client, - s []storageTypes.Storage) (*PriceFiller, error) { +func NewPriceFiller(l *zap.SugaredLogger, + s []storageTypes.Storage, + mtmClient *mtm.MtmClient, + dashboardStorage *dashboardStorage.Storage, +) (*PriceFiller, error) { p := &PriceFiller{ - l: l, - s: s, - ksClient: NewKsClient(), - binanceClient: binanceClient, + l: l, + s: s, + ksClient: NewKsClient(), mappedCoinInfo: map[string]CoinInfo{ addressETH1: { Coin: "ETH", - Network: NetworkETH, + ChainId: 1, ContractAddress: addressETH1, Decimals: 18, }, addressETH2: { Coin: "ETH", - Network: NetworkETH, + ChainId: 1, ContractAddress: addressETH2, Decimals: 18, }, }, + mtmClient: mtmClient, + dashboardStorage: dashboardStorage, } if err := p.updateAllCoinInfo(); err != nil { @@ -88,32 +90,21 @@ func (p *PriceFiller) Run() { } func (p *PriceFiller) getPrice(token string, timestamp int64) (float64, error) { - candles, err := p.binanceClient.NewKlinesService().Symbol(withAlias(token) + "USDT"). - Interval("1s").StartTime(timestamp).EndTime(timestamp).Do(context.Background()) - if err != nil { - return 0, err - } - if len(candles) == 0 { - return 0, ErrNoPrice - } - low, err := strconv.ParseFloat(candles[0].Low, 64) - if err != nil { - return 0, err - } - high, err := strconv.ParseFloat(candles[0].High, 64) + price, err := p.mtmClient.GetHistoricalRate( + context.Background(), + token, + USDTAddress, + NetworkETHChanIDString, + timestamp/1000, + ) if err != nil { return 0, err } - multiplier := 1.0 - if m, ok := mappedMultiplier[token]; ok { - multiplier = m - } - - return multiplier * (low + high) / 2, nil + return price, nil } func (p *PriceFiller) updateAllCoinInfo() error { - resp, err := p.binanceClient.NewAllCoinService().Do(context.Background()) + tokens, err := p.mtmClient.GetListTokens(context.Background()) if err != nil { p.l.Errorw("Failed to get all coins info", "err", err) return err @@ -121,23 +112,19 @@ func (p *PriceFiller) updateAllCoinInfo() error { p.mu.Lock() defer p.mu.Unlock() - for _, coinInfo := range resp { - for _, network := range coinInfo.NetworkList { - if network.Network == NetworkETH && network.ContractAddress != "" { - address := strings.ToLower(network.ContractAddress) - if _, ok := p.mappedCoinInfo[address]; !ok { - p.mappedCoinInfo[address] = CoinInfo{ - Coin: network.Coin, - Network: network.Network, - ContractAddress: address, - } - } - break - } + for _, info := range tokens { + if info.ChainId != NetworkETHChanID { + continue + } + p.mappedCoinInfo[info.Address] = CoinInfo{ + Coin: info.Symbol, + ChainId: info.ChainId, + ContractAddress: info.Address, + Decimals: info.Decimals, } - } - p.l.Infow("New mapped coin info", "data", p.mappedCoinInfo) + } + p.l.Infow("Successfully map coin info") return nil } @@ -172,6 +159,11 @@ func (p *PriceFiller) runBackFillTradelogPriceRoutine() { p.l.Infow("backfill tradelog price successfully", "exchange", s.Exchange(), "number", len(tradeLogs)) } + if err := p.insertTokens(); err != nil { + p.l.Errorw("Failed to insert tokens", "err", err) + continue + } + } } @@ -254,7 +246,7 @@ func (p *PriceFiller) getPriceAndAmountUsd(address, rawAmt string, at int64) (fl p.mu.Unlock() if ok { if coin.Decimals == 0 { - d, err := p.getDecimals(address) + decimals, symbol, err := p.getDecimalsAndSymbol(address) if err != nil { if errors.Is(err, ErrWeirdTokenCatalogResp) { return 0, 0, nil @@ -262,7 +254,8 @@ func (p *PriceFiller) getPriceAndAmountUsd(address, rawAmt string, at int64) (fl p.l.Errorw("Failed to getDecimals", "err", err, "address", address) return 0, 0, err } - coin.Decimals = d + coin.Decimals = decimals + coin.Coin = symbol p.mu.Lock() p.mappedCoinInfo[address] = coin p.mu.Unlock() @@ -271,12 +264,10 @@ func (p *PriceFiller) getPriceAndAmountUsd(address, rawAmt string, at int64) (fl if coin.Coin == coinUSDT { return 1, calculateAmountUsd(rawAmt, coin.Decimals, 1), nil } - price, err := p.getPrice(coin.Coin, at) + price, err := p.getPrice(address, at) if err != nil { - if !errors.Is(err, ErrNoPrice) { - p.l.Errorw("Failed to getPrice", "err", err, "coin", coin.Coin, "at", at) - return 0, 0, err - } + p.l.Errorw("Failed to getPrice", "err", err, "coin", coin.Coin, "at", at) + return 0, 0, err } return price, calculateAmountUsd(rawAmt, coin.Decimals, price), nil @@ -301,31 +292,48 @@ func (p *PriceFiller) FullFillTradeLogs(tradeLogs []storageTypes.TradeLog) { } } -func (p *PriceFiller) getDecimals(address string) (int64, error) { +func (p *PriceFiller) getDecimalsAndSymbol(address string) (int64, string, error) { resp, err := p.ksClient.GetTokenCatalog(address) if err != nil { p.l.Errorw("Failed to GetTokenCatalog", "err", err) - return 0, err + return 0, "", err } - if len(resp.Data.Tokens) == 1 { - return resp.Data.Tokens[0].Decimals, nil + return resp.Data.Tokens[0].Decimals, resp.Data.Tokens[0].Symbol, nil } if len(resp.Data.Tokens) > 1 { p.l.Warnw("Weird token catalog response", "resp", resp) - return 0, ErrWeirdTokenCatalogResp + return 0, "", ErrWeirdTokenCatalogResp } // try to import token if token is not found. newResp, err := p.ksClient.ImportToken(NetworkETHChanIDString, address) if err != nil { p.l.Errorw("Failed to ImportToken", "err", err) - return 0, err + return 0, "", err } if len(newResp.Data.Tokens) == 1 { - return newResp.Data.Tokens[0].Data.Decimals, nil + return newResp.Data.Tokens[0].Data.Decimals, newResp.Data.Tokens[0].Data.Symbol, nil } p.l.Warnw("Weird ImportToken response", "resp", newResp) - return 0, ErrWeirdTokenCatalogResp + return 0, "", ErrWeirdTokenCatalogResp + +} + +func (p *PriceFiller) insertTokens() error { + var tokenList []dashboardTypes.Token + for address, token := range p.mappedCoinInfo { + tokenList = append(tokenList, dashboardTypes.Token{ + Address: address, + Symbol: token.Coin, + Decimals: token.Decimals, + ChainId: NetworkETHChanID, + }) + } + err := p.dashboardStorage.InsertTokens(tokenList) + if err != nil { + return err + } + return nil } diff --git a/v2/pkg/price_filler/price_filler_test.go b/v2/pkg/price_filler/price_filler_test.go index cc862fd..4de279d 100644 --- a/v2/pkg/price_filler/price_filler_test.go +++ b/v2/pkg/price_filler/price_filler_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/KyberNetwork/go-binance/v2" + "github.com/KyberNetwork/tradelogs/v2/pkg/mtm" "github.com/KyberNetwork/tradelogs/v2/pkg/storage/tradelogs/types" "github.com/test-go/testify/require" "go.uber.org/zap" @@ -12,9 +12,9 @@ import ( // go test -v -timeout 30s -run ^TestFillPrice$ github.com/KyberNetwork/tradelogs/pkg/pricefiller func TestFillPrice(t *testing.T) { - t.Skip("Need to add Binance credentials") - bClient := binance.NewClient("", "") - filler, err := NewPriceFiller(zap.S(), bClient, nil) + t.Skip("Need to add mtm url") + mtmClient := mtm.NewMtmClient("") + filler, err := NewPriceFiller(zap.S(), nil, mtmClient, nil) if err != nil { require.NoError(t, err) } @@ -67,9 +67,9 @@ func TestFillPrice(t *testing.T) { } func TestFillBebopPrice(t *testing.T) { - //t.Skip("Need to add Binance credentials") - bClient := binance.NewClient("", "") - filler, err := NewPriceFiller(zap.S(), bClient, nil) + t.Skip("Need to add mtm url") + mtmClient := mtm.NewMtmClient("") + filler, err := NewPriceFiller(zap.S(), nil, mtmClient, nil) if err != nil { require.NoError(t, err) } diff --git a/v2/pkg/price_filler/utils.go b/v2/pkg/price_filler/utils.go index a2311c4..74829b0 100644 --- a/v2/pkg/price_filler/utils.go +++ b/v2/pkg/price_filler/utils.go @@ -6,18 +6,6 @@ import ( "github.com/KyberNetwork/tradelogs/pkg/convert" ) -var aliasCoinMap = map[string]string{ - "WETH": "ETH", - "STETH": "ETH", -} - -func withAlias(coin string) string { - if s, ok := aliasCoinMap[coin]; ok { - return s - } - return coin -} - // calculateAmountUsd returns raw / (10**decimals) * price func calculateAmountUsd(raw string, decimals int64, price float64) float64 { rawAmt, ok := new(big.Int).SetString(raw, 10) diff --git a/v2/pkg/storage/dashboard/storage.go b/v2/pkg/storage/dashboard/storage.go new file mode 100644 index 0000000..540f354 --- /dev/null +++ b/v2/pkg/storage/dashboard/storage.go @@ -0,0 +1,138 @@ +package dashboard + +import ( + "reflect" + "strings" + + types "github.com/KyberNetwork/tradelogs/v2/pkg/storage/dashboard/types" + "github.com/Masterminds/squirrel" + "github.com/jmoiron/sqlx" + "go.uber.org/zap" +) + +const ( + tokenTable = "token" + contractTable = "contract" + eventTable = "event" +) + +type Storage struct { + db *sqlx.DB + l *zap.SugaredLogger +} + +func New(l *zap.SugaredLogger, db *sqlx.DB) *Storage { + return &Storage{ + db: db, + l: l, + } +} + +// ----------------------------insert---------------------------- +func (s *Storage) InsertTokens(tokens []types.Token) error { + if len(tokens) == 0 { + return nil + } + + b := squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar).Insert(tokenTable).Columns( + types.TokenColumns()..., + ) + for _, token := range tokens { + b = b.Values( + token.SerializeToken()..., + ) + } + q, p, err := b.Suffix(`ON CONFLICT (address, chain_id) DO UPDATE + SET + symbol=excluded.symbol, + decimals=excluded.decimals + `).ToSql() + if err != nil { + s.l.Errorw("Error build insert", "error", err) + return err + } + if _, err := s.db.Exec(q, p...); err != nil { + s.l.Errorw("Error exec insert", "sql", q, "arg", p, "error", err) + return err + } + return nil +} + +func (s *Storage) InsertContract(contracts []types.Contract) error { + if len(contracts) == 0 { + return nil + } + + b := squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar).Insert(contractTable).Columns( + types.ContractColumns()..., + ) + for _, contract := range contracts { + b = b.Values( + contract.SerializeContract()..., + ) + } + q, p, err := b.Suffix(`ON CONFLICT (contract) DO UPDATE + SET + contract_name=excluded.contract_name + `).ToSql() + if err != nil { + s.l.Errorw("Error build insert", "error", err) + return err + } + if _, err := s.db.Exec(q, p...); err != nil { + s.l.Errorw("Error exec insert", "sql", q, "arg", p, "error", err) + return err + } + return nil +} + +// ----------------------------get---------------------------- +func (s *Storage) GetTokens(query types.TokenQuery) ([]types.Token, error) { + builder := squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar). + Select(types.TokenColumns()...). + From(tokenTable) + + v := reflect.ValueOf(query) + fields := v.Type() + for i := 0; i < v.NumField(); i++ { + tag := string(fields.Field(i).Tag.Get("form")) + if v.Field(i).IsZero() { + continue + } + if tag == "symbol" { + builder = builder.Where(squirrel.Eq{tag: strings.ToUpper(v.Field(i).String())}) + continue + } + builder = builder.Where(squirrel.Eq{tag: strings.ToLower(v.Field(i).String())}) + } + + q, p, err := builder.ToSql() + if err != nil { + return nil, err + } + + var tokens []types.Token + if err := s.db.Select(&tokens, q, p...); err != nil { + return nil, err + } + + return tokens, nil +} + +func (s *Storage) GetContracts() ([]types.Contract, error) { + builder := squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar). + Select(types.ContractColumns()...). + From(contractTable) + + q, p, err := builder.ToSql() + if err != nil { + return nil, err + } + + var contracts []types.Contract + if err := s.db.Select(&contracts, q, p...); err != nil { + return nil, err + } + + return contracts, nil +} diff --git a/v2/pkg/storage/dashboard/types/contract.go b/v2/pkg/storage/dashboard/types/contract.go new file mode 100644 index 0000000..cd253a7 --- /dev/null +++ b/v2/pkg/storage/dashboard/types/contract.go @@ -0,0 +1,22 @@ +package types + +import "strings" + +type Contract struct { + Contract string `db:"contract" json:"contract"` + ContractName string `db:"contract_name" json:"contract_name"` +} + +func (o *Contract) SerializeContract() []interface{} { + return []interface{}{ + strings.ToLower(o.Contract), + o.ContractName, + } +} + +func ContractColumns() []string { + return []string{ + "contract", + "contract_name", + } +} diff --git a/v2/pkg/storage/dashboard/types/token.go b/v2/pkg/storage/dashboard/types/token.go new file mode 100644 index 0000000..52aeef6 --- /dev/null +++ b/v2/pkg/storage/dashboard/types/token.go @@ -0,0 +1,37 @@ +package types + +import ( + "strconv" + "strings" +) + +// -------------table------------- +type Token struct { + Address string `db:"address" json:"address,omitempty"` + ChainId int64 `db:"chain_id" json:"chain_id,omitempty"` + Symbol string `db:"symbol" json:"symbol,omitempty"` + Decimals int64 `db:"decimals" json:"decimals,omitempty"` +} + +type TokenQuery struct { + Address string `form:"address" json:"address,omitempty"` + Symbol string `form:"symbol" json:"symbol,omitempty"` +} + +func (o *Token) SerializeToken() []interface{} { + return []interface{}{ + strings.ToLower(o.Address), + strconv.FormatInt(o.ChainId, 10), + strings.ToUpper(o.Symbol), + o.Decimals, + } +} + +func TokenColumns() []string { + return []string{ + "address", + "chain_id", + "symbol", + "decimals", + } +}