88 "math/big"
99 "net/http"
1010 "strconv"
11+ "sync"
1112 "time"
1213
1314 "github.com/morph-l2/go-ethereum/log"
@@ -18,10 +19,12 @@ const (
1819)
1920
2021// BitgetSDKPriceFeed uses Bitget REST API to fetch prices
22+ // This type is safe for concurrent use by multiple goroutines
2123type BitgetSDKPriceFeed struct {
2224 httpClient * http.Client
23- tokenMap map [uint16 ]string
24- ethPrice * big.Float
25+ mu sync.RWMutex // protects tokenMap and ethPrice
26+ tokenMap map [uint16 ]string // guarded by mu
27+ ethPrice * big.Float // guarded by mu
2528 log log.Logger
2629 baseURL string
2730}
@@ -61,7 +64,11 @@ func NewBitgetSDKPriceFeed(tokenMap map[uint16]string, baseURL string) *BitgetSD
6164// GetTokenPrice returns token price in USD
6265// Note: Caller should ensure ETH price is updated via GetBatchTokenPrices for batch operations
6366func (b * BitgetSDKPriceFeed ) GetTokenPrice (ctx context.Context , tokenID uint16 ) (* TokenPrice , error ) {
67+ b .mu .RLock ()
6468 symbol , exists := b .tokenMap [tokenID ]
69+ ethPrice := new (big.Float ).Copy (b .ethPrice )
70+ b .mu .RUnlock ()
71+
6572 if ! exists {
6673 return nil , fmt .Errorf ("token ID %d not mapped to trading pair" , tokenID )
6774 }
@@ -73,7 +80,7 @@ func (b *BitgetSDKPriceFeed) GetTokenPrice(ctx context.Context, tokenID uint16)
7380 }
7481
7582 // Use cached ETH price (should be updated by GetBatchTokenPrices)
76- if b . ethPrice .Cmp (big .NewFloat (0 )) == 0 {
83+ if ethPrice .Cmp (big .NewFloat (0 )) == 0 {
7784 return nil , fmt .Errorf ("ETH price not initialized, please call GetBatchTokenPrices first" )
7885 }
7986
@@ -82,18 +89,19 @@ func (b *BitgetSDKPriceFeed) GetTokenPrice(ctx context.Context, tokenID uint16)
8289 "token_id" , tokenID ,
8390 "symbol" , symbol ,
8491 "token_price_usd" , tokenPrice .String (),
85- "eth_price_usd" , b . ethPrice .String ())
92+ "eth_price_usd" , ethPrice .String ())
8693
8794 return & TokenPrice {
8895 TokenID : tokenID ,
8996 Symbol : symbol ,
9097 TokenPriceUSD : tokenPrice ,
91- EthPriceUSD : b . ethPrice ,
98+ EthPriceUSD : ethPrice ,
9299 }, nil
93100}
94101
95102// GetBatchTokenPrices returns batch token prices in USD
96103func (b * BitgetSDKPriceFeed ) GetBatchTokenPrices (ctx context.Context , tokenIDs []uint16 ) (map [uint16 ]* TokenPrice , error ) {
104+ // Update ETH price first (this will acquire write lock)
97105 if err := b .updateETHPrice (ctx ); err != nil {
98106 return nil , fmt .Errorf ("failed to update ETH price: %w" , err )
99107 }
@@ -121,7 +129,10 @@ func (b *BitgetSDKPriceFeed) updateETHPrice(ctx context.Context) error {
121129 return fmt .Errorf ("failed to fetch ETH price: %w" , err )
122130 }
123131
132+ b .mu .Lock ()
124133 b .ethPrice = price
134+ b .mu .Unlock ()
135+
125136 b .log .Info ("Fetched ETH price from Bitget" ,
126137 "source" , "bitget" ,
127138 "symbol" , "ETHUSDT" ,
@@ -227,16 +238,27 @@ func (b *BitgetSDKPriceFeed) fetchPriceOnce(ctx context.Context, symbol string)
227238}
228239
229240// UpdateTokenMap updates token mapping
241+ // This method is safe to call concurrently with other methods
242+ // The input map is copied to prevent external modifications
230243func (b * BitgetSDKPriceFeed ) UpdateTokenMap (tokenMap map [uint16 ]string ) {
231- b .tokenMap = tokenMap
232- b .log .Info ("Updated token map" , "token_map" , tokenMap )
244+ b .mu .Lock ()
245+ // Create a defensive copy to prevent external modifications
246+ copied := make (map [uint16 ]string , len (tokenMap ))
247+ for k , v := range tokenMap {
248+ copied [k ] = v
249+ }
250+ b .tokenMap = copied
251+ b .mu .Unlock ()
252+ b .log .Info ("Updated token map" , "token_map" , copied )
233253}
234254
235255// GetSupportedTokens returns list of supported token IDs
236256func (b * BitgetSDKPriceFeed ) GetSupportedTokens () []uint16 {
257+ b .mu .RLock ()
237258 tokenIDs := make ([]uint16 , 0 , len (b .tokenMap ))
238259 for tokenID := range b .tokenMap {
239260 tokenIDs = append (tokenIDs , tokenID )
240261 }
262+ b .mu .RUnlock ()
241263 return tokenIDs
242264}
0 commit comments