/
autoconvertedprice.go
139 lines (119 loc) · 4.18 KB
/
autoconvertedprice.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package core
import (
"context"
"fmt"
"math/big"
"strings"
"sync"
"github.com/livepeer/go-livepeer/eth"
"github.com/livepeer/go-livepeer/eth/watchers"
)
// PriceFeedWatcher is a global instance of a PriceFeedWatcher. It must be
// initialized before creating an AutoConvertedPrice instance.
var PriceFeedWatcher watchers.PriceFeedWatcher
// Number of wei in 1 ETH
var weiPerETH = big.NewRat(1e18, 1)
// AutoConvertedPrice represents a price that is automatically converted to wei
// based on the current price of ETH in a given currency. It uses the static
// PriceFeedWatcher that must be configured before creating an instance.
type AutoConvertedPrice struct {
cancelSubscription func()
onUpdate func(*big.Rat)
basePrice *big.Rat
mu sync.RWMutex
current *big.Rat
}
// NewFixedPrice creates a new AutoConvertedPrice with a fixed price in wei.
func NewFixedPrice(price *big.Rat) *AutoConvertedPrice {
return &AutoConvertedPrice{current: price}
}
// NewAutoConvertedPrice creates a new AutoConvertedPrice instance with the given
// currency and base price. The onUpdate function is optional and gets called
// whenever the price is updated (also with the initial price). The Stop function
// must be called to free resources when the price is no longer needed.
func NewAutoConvertedPrice(currency string, basePrice *big.Rat, onUpdate func(*big.Rat)) (*AutoConvertedPrice, error) {
if onUpdate == nil {
onUpdate = func(*big.Rat) {}
}
// Default currency (wei/eth) doesn't need the conversion loop
if lcurr := strings.ToLower(currency); lcurr == "" || lcurr == "wei" || lcurr == "eth" {
price := basePrice
if lcurr == "eth" {
price = new(big.Rat).Mul(basePrice, weiPerETH)
}
onUpdate(price)
return NewFixedPrice(price), nil
}
if PriceFeedWatcher == nil {
return nil, fmt.Errorf("PriceFeedWatcher is not initialized")
}
base, quote, err := PriceFeedWatcher.Currencies()
if err != nil {
return nil, fmt.Errorf("error getting price feed currencies: %v", err)
}
base, quote, currency = strings.ToUpper(base), strings.ToUpper(quote), strings.ToUpper(currency)
if base != "ETH" && quote != "ETH" {
return nil, fmt.Errorf("price feed does not have ETH as a currency (%v/%v)", base, quote)
}
if base != currency && quote != currency {
return nil, fmt.Errorf("price feed does not have %v as a currency (%v/%v)", currency, base, quote)
}
currencyPrice, err := PriceFeedWatcher.Current()
if err != nil {
return nil, fmt.Errorf("error getting current price data: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
price := &AutoConvertedPrice{
cancelSubscription: cancel,
onUpdate: onUpdate,
basePrice: basePrice,
current: new(big.Rat).Mul(basePrice, currencyToWeiMultiplier(currencyPrice, base)),
}
// Trigger the initial update with the current price
onUpdate(price.current)
price.startAutoConvertLoop(ctx, base)
return price, nil
}
// Value returns the current price in wei.
func (a *AutoConvertedPrice) Value() *big.Rat {
a.mu.RLock()
defer a.mu.RUnlock()
return a.current
}
// Stop unsubscribes from the price feed and frees resources from the
// auto-conversion loop.
func (a *AutoConvertedPrice) Stop() {
a.mu.Lock()
defer a.mu.Unlock()
if a.cancelSubscription != nil {
a.cancelSubscription()
a.cancelSubscription = nil
}
}
func (a *AutoConvertedPrice) startAutoConvertLoop(ctx context.Context, baseCurrency string) {
priceUpdated := make(chan eth.PriceData, 1)
PriceFeedWatcher.Subscribe(ctx, priceUpdated)
go func() {
for {
select {
case <-ctx.Done():
return
case currencyPrice := <-priceUpdated:
a.mu.Lock()
a.current = new(big.Rat).Mul(a.basePrice, currencyToWeiMultiplier(currencyPrice, baseCurrency))
a.mu.Unlock()
a.onUpdate(a.current)
}
}
}()
}
// currencyToWeiMultiplier calculates the multiplier to convert the value
// specified in the custom currency to wei.
func currencyToWeiMultiplier(data eth.PriceData, baseCurrency string) *big.Rat {
ethMultipler := data.Price
if baseCurrency == "ETH" {
// Invert the multiplier if the quote is in the form ETH / X
ethMultipler = new(big.Rat).Inv(ethMultipler)
}
return new(big.Rat).Mul(ethMultipler, weiPerETH)
}