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.

136 lines
2.8 KiB

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