Skip to content
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

Pass $ alongside TVL #489 #202

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
8 changes: 7 additions & 1 deletion config/config-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,10 @@ assets:
ordinals:
host: "http://ord-poc.devnet.babylonchain.io"
port: 8888
timeout: 1000
timeout: 1000
external_apis:
coinmarketcap:
api_key: ${COINMARKETCAP_API_KEY}
base_url: "https://pro-api.coinmarketcap.com/v1"
timeout: 10s # http client timeout
cache_ttl: 300s # mongodb ttl
8 changes: 7 additions & 1 deletion config/config-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,10 @@ assets:
ordinals:
host: "http://ord-poc.devnet.babylonchain.io"
port: 8888
timeout: 5000
timeout: 5000
external_apis:
coinmarketcap:
api_key: ${COINMARKETCAP_API_KEY}
base_url: "https://pro-api.coinmarketcap.com/v1"
timeout: 10s # http client timeout
cache_ttl: 300s # mongodb ttl
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/btcsuite/btcd/btcutil v1.1.6
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
github.com/miguelmota/go-coinmarketcap v0.1.8
github.com/robfig/cron/v3 v3.0.1
github.com/spf13/viper v1.19.0
github.com/swaggo/swag v1.16.3
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/anaskhan96/soup v1.0.1/go.mod h1:pT5vs4HXDwA5y4KQCsKvnkpQd3D+joP7IqpiGskfWW0=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
Expand Down Expand Up @@ -569,6 +570,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miguelmota/go-coinmarketcap v0.1.8 h1:rZhB7xs1j7qxxd1zftjADhAv6ECJQVhBom1dh3zURKY=
github.com/miguelmota/go-coinmarketcap v0.1.8/go.mod h1:hBjej1IiB5+pfj+0cZhnxRkAc2bgky8qWLhCJTQ3zjw=
github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q=
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
Expand Down Expand Up @@ -895,6 +898,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180215212450-dc948dff8834/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down
8 changes: 8 additions & 0 deletions internal/shared/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Config struct {
Metrics *MetricsConfig `mapstructure:"metrics"`
Assets *AssetsConfig `mapstructure:"assets"`
DelegationTransition *DelegationTransitionConfig `mapstructure:"delegation-transition"`
ExternalAPIs *ExternalAPIsConfig `mapstructure:"external_apis"`
}

func (cfg *Config) Validate() error {
Expand Down Expand Up @@ -53,6 +54,13 @@ func (cfg *Config) Validate() error {
}
}

// ExternalAPIs is optional
if cfg.ExternalAPIs != nil {
if err := cfg.ExternalAPIs.Validate(); err != nil {
return err
}
}

return nil
}

Expand Down
45 changes: 45 additions & 0 deletions internal/shared/config/external_apis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package config

import (
"fmt"
"time"
)

type ExternalAPIsConfig struct {
CoinMarketCap *CoinMarketCapConfig `mapstructure:"coinmarketcap"`
}

type CoinMarketCapConfig struct {
APIKey string `mapstructure:"api_key"`
BaseURL string `mapstructure:"base_url"`
Timeout time.Duration `mapstructure:"timeout"`
CacheTTL time.Duration `mapstructure:"cache_ttl"`
}

func (cfg *ExternalAPIsConfig) Validate() error {
if cfg.CoinMarketCap == nil {
return fmt.Errorf("missing coinmarketcap config")
}

return cfg.CoinMarketCap.Validate()
}

func (cfg *CoinMarketCapConfig) Validate() error {
if cfg.APIKey == "" {
return fmt.Errorf("missing coinmarketcap api key")
}

if cfg.BaseURL == "" {
return fmt.Errorf("missing coinmarketcap base url")
}

if cfg.Timeout <= 0 {
return fmt.Errorf("invalid coinmarketcap timeout")
}

if cfg.CacheTTL <= 0 {
return fmt.Errorf("invalid coinmarketcap cache ttl")
}

return nil
}
32 changes: 32 additions & 0 deletions internal/shared/db/client/btc_price.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package dbclient

import (
"context"
model "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
"time"
)

func (db *Database) GetLatestBtcPrice(ctx context.Context) (*model.BtcPrice, error) {
client := db.Client.Database(db.DbName).Collection(model.BtcPriceCollection)
var btcPrice model.BtcPrice
err := client.FindOne(ctx, bson.M{"_id": model.BtcPriceDocID}).Decode(&btcPrice)
if err != nil {
return nil, err
}
return &btcPrice, nil
}
func (db *Database) SetBtcPrice(ctx context.Context, price float64) error {
client := db.Client.Database(db.DbName).Collection(model.BtcPriceCollection)
btcPrice := model.BtcPrice{
ID: model.BtcPriceDocID, // Fixed ID for single document
Price: price,
CreatedAt: time.Now(), // For TTL index
}
opts := options.Update().SetUpsert(true)
filter := bson.M{"_id": model.BtcPriceDocID}
update := bson.M{"$set": btcPrice}
_, err := client.UpdateOne(ctx, filter, update, opts)
return err
}
5 changes: 5 additions & 0 deletions internal/shared/db/client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,9 @@ type DBClient interface {
SaveUnprocessableMessage(ctx context.Context, messageBody, receipt string) error
FindUnprocessableMessages(ctx context.Context) ([]dbmodel.UnprocessableMessageDocument, error)
DeleteUnprocessableMessage(ctx context.Context, Receipt interface{}) error

// GetLatestBtcPrice fetches the BTC price from the database.
GetLatestBtcPrice(ctx context.Context) (*dbmodel.BtcPrice, error)
// SetBtcPrice sets the latest BTC price in the database.
SetBtcPrice(ctx context.Context, price float64) error
}
11 changes: 11 additions & 0 deletions internal/shared/db/model/btc_price.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dbmodel

import "time"

const BtcPriceDocID = "btc_price"

type BtcPrice struct {
ID string `bson:"_id"` // primary key, will always be "btc_price" to ensure single document
Price float64 `bson:"price"`
CreatedAt time.Time `bson:"created_at"` // TTL index will be on this field
}
31 changes: 31 additions & 0 deletions internal/shared/db/model/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"strings"
)

const (
Expand All @@ -26,6 +27,7 @@ const (
V1UnbondingCollection = "unbonding_queue"
V1BtcInfoCollection = "btc_info"
V1UnprocessableMsgCollection = "unprocessable_messages"
BtcPriceCollection = "btc_price"
// V2
V2StatsLockCollection = "v2_stats_lock"
V2OverallStatsCollection = "v2_overall_stats"
Expand Down Expand Up @@ -93,6 +95,14 @@ func Setup(ctx context.Context, cfg *config.Config) error {
}
}

// If external APIs are configured, create TTL index for BTC price collection
if cfg.ExternalAPIs != nil {
if err := createTTLIndexes(ctx, database, cfg.ExternalAPIs.CoinMarketCap.CacheTTL); err != nil {
log.Error().Err(err).Msg("Failed to create TTL index for BTC price")
return err
}
}

log.Info().Msg("Collections and Indexes created successfully.")
return nil
}
Expand Down Expand Up @@ -135,3 +145,24 @@ func createIndex(ctx context.Context, database *mongo.Database, collectionName s

log.Debug().Msg("Index created successfully on collection: " + collectionName)
}

func createTTLIndexes(ctx context.Context, database *mongo.Database, cacheTTL time.Duration) error {
collection := database.Collection(BtcPriceCollection)
// First, drop the existing TTL index if it exists
_, err := collection.Indexes().DropOne(ctx, "created_at_1")
if err != nil && !strings.Contains(err.Error(), "not found") {
return fmt.Errorf("failed to drop existing TTL index: %w", err)
}
// Create new TTL index
model := mongo.IndexModel{
Keys: bson.D{{Key: "created_at", Value: 1}},
Options: options.Index().
SetExpireAfterSeconds(int32(cacheTTL.Seconds())).
SetName("created_at_1"),
}
_, err = collection.Indexes().CreateOne(ctx, model)
if err != nil {
return fmt.Errorf("failed to create TTL index: %w", err)
}
return nil
}
64 changes: 64 additions & 0 deletions internal/shared/http/clients/coinmarketcap/coinmarketcap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package coinmarketcap

import (
"context"
"net/http"

"github.com/babylonlabs-io/staking-api-service/internal/shared/config"
"github.com/babylonlabs-io/staking-api-service/internal/shared/types"
)

type CoinMarketCapClient struct {
config *config.CoinMarketCapConfig
defaultHeaders map[string]string
httpClient *http.Client
}

type CMCResponse struct {
Data map[string]CryptoData `json:"data"`
}

type CryptoData struct {
Quote map[string]QuoteData `json:"quote"`
}

type QuoteData struct {
Price float64 `json:"price"`
}

func NewCoinMarketCapClient(config *config.CoinMarketCapConfig) *CoinMarketCapClient {
// Client is disabled if config is nil
if config == nil {
return nil
}

httpClient := &http.Client{}
headers := map[string]string{
"X-CMC_PRO_API_KEY": config.APIKey,
"Accept": "application/json",
}

return &CoinMarketCapClient{
config,
headers,
httpClient,
}
}

// Necessary for the BaseClient interface
func (c *CoinMarketCapClient) GetBaseURL() string {
return c.config.BaseURL
}

func (c *CoinMarketCapClient) GetDefaultRequestTimeout() int {
return int(c.config.Timeout.Milliseconds())
}

func (c *CoinMarketCapClient) GetHttpClient() *http.Client {
return c.httpClient
}

func (c *CoinMarketCapClient) GetLatestBtcPrice(ctx context.Context) (float64, *types.Error) {
// todo implement me
return 0, nil
}
12 changes: 12 additions & 0 deletions internal/shared/http/clients/coinmarketcap/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package coinmarketcap

import (
"context"

"github.com/babylonlabs-io/staking-api-service/internal/shared/types"
)

//go:generate mockery --name=CoinMarketCapClientInterface --output=../../../../../tests/mocks --outpkg=mocks --filename=mock_coinmarketcap_client.go
type CoinMarketCapClientInterface interface {
GetLatestBtcPrice(ctx context.Context) (float64, *types.Error)
}
14 changes: 12 additions & 2 deletions internal/shared/http/clients/http_clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package clients
import (
"github.com/babylonlabs-io/staking-api-service/internal/shared/config"
"github.com/babylonlabs-io/staking-api-service/internal/shared/http/clients/ordinals"
cmc "github.com/miguelmota/go-coinmarketcap/pro/v1"
)

type Clients struct {
Ordinals ordinals.OrdinalsClient
Ordinals ordinals.OrdinalsClient
CoinMarketCap *cmc.Client // todo for review: move to another location?
}

func New(cfg *config.Config) *Clients {
Expand All @@ -16,7 +18,15 @@ func New(cfg *config.Config) *Clients {
ordinalsClient = ordinals.New(cfg.Assets.Ordinals)
}

var cmcClient *cmc.Client
if cfg.ExternalAPIs != nil && cfg.ExternalAPIs.CoinMarketCap != nil {
cmcClient = cmc.NewClient(&cmc.Config{
ProAPIKey: cfg.ExternalAPIs.CoinMarketCap.APIKey,
})
}

return &Clients{
Ordinals: ordinalsClient,
Ordinals: ordinalsClient,
CoinMarketCap: cmcClient,
}
}
Loading
Loading