You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

130 lines
2.9 KiB

  1. package priceupdater
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "sync"
  7. "time"
  8. "github.com/dghubble/sling"
  9. )
  10. const (
  11. defaultMaxIdleConns = 10
  12. defaultIdleConnTimeout = 10
  13. )
  14. var (
  15. // ErrSymbolDoesNotExistInDatabase is used when trying to get a token that is not in the DB
  16. ErrSymbolDoesNotExistInDatabase = errors.New("symbol does not exist in database")
  17. )
  18. // ConfigPriceUpdater contains the configuration set by the coordinator
  19. type ConfigPriceUpdater struct {
  20. RecommendedFee uint64 // in dollars
  21. RecommendedCreateAccountFee uint64 // in dollars
  22. TokensList []string
  23. APIURL string
  24. }
  25. // TokenInfo contains the updated value for the token
  26. type TokenInfo struct {
  27. Symbol string
  28. Value float64
  29. LastUpdated time.Time
  30. }
  31. // PriceUpdater definition
  32. type PriceUpdater struct {
  33. db map[string]TokenInfo
  34. config ConfigPriceUpdater
  35. mu sync.RWMutex
  36. }
  37. // NewPriceUpdater is the constructor for the updater
  38. func NewPriceUpdater(config ConfigPriceUpdater) PriceUpdater {
  39. return PriceUpdater{
  40. db: make(map[string]TokenInfo),
  41. config: config,
  42. }
  43. }
  44. // UpdatePrices is triggered by the Coordinator, and internally will update the token prices in the db
  45. func (p *PriceUpdater) UpdatePrices() error {
  46. tr := &http.Transport{
  47. MaxIdleConns: defaultMaxIdleConns,
  48. IdleConnTimeout: defaultIdleConnTimeout * time.Second,
  49. DisableCompression: true,
  50. }
  51. httpClient := &http.Client{Transport: tr}
  52. client := sling.New().Base(p.config.APIURL).Client(httpClient)
  53. state := [10]float64{}
  54. for _, tokenSymbol := range p.config.TokensList {
  55. resp, err := client.New().Get("ticker/t" + tokenSymbol + "USD").ReceiveSuccess(&state)
  56. if err != nil {
  57. return err
  58. }
  59. // if resp.StatusCode != 200 {
  60. if resp.StatusCode != http.StatusOK {
  61. return fmt.Errorf("Unexpected response status code: %v", resp.StatusCode)
  62. }
  63. tinfo := TokenInfo{
  64. Symbol: tokenSymbol,
  65. Value: state[6],
  66. LastUpdated: time.Now(),
  67. }
  68. p.UpdateTokenInfo(tinfo)
  69. }
  70. return nil
  71. }
  72. // UpdateConfig allows to update the price-updater configuration
  73. func (p *PriceUpdater) UpdateConfig(config ConfigPriceUpdater) {
  74. p.mu.Lock()
  75. defer p.mu.Unlock()
  76. p.config = config
  77. }
  78. // Get one token information
  79. func (p *PriceUpdater) Get(tokenSymbol string) (TokenInfo, error) {
  80. var info TokenInfo
  81. // Check if symbol exists in database
  82. p.mu.RLock()
  83. defer p.mu.RUnlock()
  84. if info, ok := p.db[tokenSymbol]; ok {
  85. return info, nil
  86. }
  87. return info, ErrSymbolDoesNotExistInDatabase
  88. }
  89. // GetPrices gets all the prices contained in the db
  90. func (p *PriceUpdater) GetPrices() map[string]TokenInfo {
  91. var info = make(map[string]TokenInfo)
  92. p.mu.RLock()
  93. defer p.mu.RUnlock()
  94. for key, value := range p.db {
  95. info[key] = value
  96. }
  97. return info
  98. }
  99. // UpdateTokenInfo updates one token info
  100. func (p *PriceUpdater) UpdateTokenInfo(tokenInfo TokenInfo) {
  101. p.mu.Lock()
  102. defer p.mu.Unlock()
  103. p.db[tokenInfo.Symbol] = tokenInfo
  104. }