diff --git a/Makefile b/Makefile index 8860581c..db68ab8a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ ################## update dependencies #################### -ETHEREUM_SUBMODULE_COMMIT_OR_TAG := d085f8c79a53edbd45c4af09f8a8182f1b1d5401 -ETHEREUM_TARGET_VERSION := v1.10.14-0.20251119080508-d085f8c79a53 +ETHEREUM_SUBMODULE_COMMIT_OR_TAG := 49fa27bcab243c67f6489de504453637d6015ccf +ETHEREUM_TARGET_VERSION := v1.10.14-0.20251203083507-49fa27bcab24 TENDERMINT_TARGET_VERSION := v0.3.2 ETHEREUM_MODULE_NAME := github.com/morph-l2/go-ethereum diff --git a/bindings/go.mod b/bindings/go.mod index 7e4f03c6..938251fd 100644 --- a/bindings/go.mod +++ b/bindings/go.mod @@ -4,7 +4,7 @@ go 1.24.0 replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.2 -require github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 +require github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 require ( github.com/VictoriaMetrics/fastcache v1.12.2 // indirect diff --git a/bindings/go.sum b/bindings/go.sum index fe0add30..5e2b2fc5 100644 --- a/bindings/go.sum +++ b/bindings/go.sum @@ -111,8 +111,8 @@ github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqky github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 h1:8+qaUTn1/eyS8er4RkibhHMFC/L4IgqIXLtORakBDkI= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 h1:r9eaQDNgjAxsuUchmoCFaAjL1TmUfjAmIlJjAtgUk8U= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= diff --git a/contracts/go.mod b/contracts/go.mod index 1c7dab20..8fdb65fe 100644 --- a/contracts/go.mod +++ b/contracts/go.mod @@ -6,7 +6,7 @@ replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3. require ( github.com/iden3/go-iden3-crypto v0.0.16 - github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 + github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 github.com/stretchr/testify v1.10.0 ) diff --git a/contracts/go.sum b/contracts/go.sum index ec6efedf..d2ca2973 100644 --- a/contracts/go.sum +++ b/contracts/go.sum @@ -138,8 +138,8 @@ github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqky github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 h1:8+qaUTn1/eyS8er4RkibhHMFC/L4IgqIXLtORakBDkI= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 h1:r9eaQDNgjAxsuUchmoCFaAjL1TmUfjAmIlJjAtgUk8U= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= diff --git a/node/go.mod b/node/go.mod index a14a5471..e9044e9e 100644 --- a/node/go.mod +++ b/node/go.mod @@ -11,7 +11,7 @@ require ( github.com/hashicorp/golang-lru v1.0.2 github.com/holiman/uint256 v1.2.4 github.com/klauspost/compress v1.17.9 - github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 + github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 github.com/prometheus/client_golang v1.17.0 github.com/spf13/viper v1.13.0 github.com/stretchr/testify v1.10.0 diff --git a/node/go.sum b/node/go.sum index f9673a5d..1ce48a98 100644 --- a/node/go.sum +++ b/node/go.sum @@ -361,8 +361,8 @@ github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqky github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 h1:8+qaUTn1/eyS8er4RkibhHMFC/L4IgqIXLtORakBDkI= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 h1:r9eaQDNgjAxsuUchmoCFaAjL1TmUfjAmIlJjAtgUk8U= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= github.com/morph-l2/tendermint v0.3.2 h1:Gu6Uj2G6c3YP2NAKFi7A46JZaOCdD4zfZDKCjt0pDm8= github.com/morph-l2/tendermint v0.3.2/go.mod h1:TtCzp9l6Z6yDUiwv3TbqKqw8Q8RKp3fSz5+adO1/Y8w= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= diff --git a/ops/l2-genesis/go.mod b/ops/l2-genesis/go.mod index 592cfc32..584f4fa1 100644 --- a/ops/l2-genesis/go.mod +++ b/ops/l2-genesis/go.mod @@ -6,7 +6,7 @@ replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3. require ( github.com/holiman/uint256 v1.2.4 - github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 + github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 github.com/stretchr/testify v1.10.0 github.com/urfave/cli v1.22.17 ) diff --git a/ops/l2-genesis/go.sum b/ops/l2-genesis/go.sum index 880d32dd..fc8c75a2 100644 --- a/ops/l2-genesis/go.sum +++ b/ops/l2-genesis/go.sum @@ -141,8 +141,8 @@ github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqky github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 h1:8+qaUTn1/eyS8er4RkibhHMFC/L4IgqIXLtORakBDkI= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 h1:r9eaQDNgjAxsuUchmoCFaAjL1TmUfjAmIlJjAtgUk8U= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= diff --git a/ops/tools/go.mod b/ops/tools/go.mod index a3d7fd3e..dc9e7d07 100644 --- a/ops/tools/go.mod +++ b/ops/tools/go.mod @@ -5,7 +5,7 @@ go 1.24.0 replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.2 require ( - github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 + github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 github.com/tendermint/tendermint v0.35.9 ) diff --git a/ops/tools/go.sum b/ops/tools/go.sum index 5c2f8a89..09537f28 100644 --- a/ops/tools/go.sum +++ b/ops/tools/go.sum @@ -163,8 +163,8 @@ github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqky github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 h1:8+qaUTn1/eyS8er4RkibhHMFC/L4IgqIXLtORakBDkI= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 h1:r9eaQDNgjAxsuUchmoCFaAjL1TmUfjAmIlJjAtgUk8U= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= github.com/morph-l2/tendermint v0.3.2 h1:Gu6Uj2G6c3YP2NAKFi7A46JZaOCdD4zfZDKCjt0pDm8= github.com/morph-l2/tendermint v0.3.2/go.mod h1:TtCzp9l6Z6yDUiwv3TbqKqw8Q8RKp3fSz5+adO1/Y8w= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= diff --git a/oracle/go.mod b/oracle/go.mod index c596a244..37fe61c2 100644 --- a/oracle/go.mod +++ b/oracle/go.mod @@ -7,7 +7,7 @@ replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3. require ( github.com/go-kit/kit v0.12.0 github.com/morph-l2/externalsign v0.3.1 - github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 + github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 github.com/prometheus/client_golang v1.17.0 github.com/stretchr/testify v1.10.0 github.com/tendermint/tendermint v0.35.9 diff --git a/oracle/go.sum b/oracle/go.sum index fbf4afb5..4b963fd5 100644 --- a/oracle/go.sum +++ b/oracle/go.sum @@ -174,8 +174,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morph-l2/externalsign v0.3.1 h1:UYFDZFB0L85A4rDvuwLNBiGEi0kSmg9AZ2v8Q5O4dQo= github.com/morph-l2/externalsign v0.3.1/go.mod h1:b6NJ4GUiiG/gcSJsp3p8ExsIs4ZdphlrVALASnVoGJE= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 h1:8+qaUTn1/eyS8er4RkibhHMFC/L4IgqIXLtORakBDkI= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 h1:r9eaQDNgjAxsuUchmoCFaAjL1TmUfjAmIlJjAtgUk8U= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= github.com/morph-l2/tendermint v0.3.2 h1:Gu6Uj2G6c3YP2NAKFi7A46JZaOCdD4zfZDKCjt0pDm8= github.com/morph-l2/tendermint v0.3.2/go.mod h1:TtCzp9l6Z6yDUiwv3TbqKqw8Q8RKp3fSz5+adO1/Y8w= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= diff --git a/token-price-oracle/client/bitget_sdk.go b/token-price-oracle/client/bitget_sdk.go index 6868672a..1f92f33a 100644 --- a/token-price-oracle/client/bitget_sdk.go +++ b/token-price-oracle/client/bitget_sdk.go @@ -8,6 +8,7 @@ import ( "math/big" "net/http" "strconv" + "strings" "sync" "time" @@ -16,6 +17,10 @@ import ( const ( bitgetTickerPath = "/api/v2/spot/market/tickers" + + // StablecoinPrefix is used to mark stablecoins with fixed USD price + // Format: "$1.0" means the token is pegged to $1.0 USD + StablecoinPrefix = "$" ) // BitgetSDKPriceFeed uses Bitget REST API to fetch prices @@ -63,6 +68,10 @@ func NewBitgetSDKPriceFeed(tokenMap map[uint16]string, baseURL string) *BitgetSD // GetTokenPrice returns token price in USD // Note: Caller should ensure ETH price is updated via GetBatchTokenPrices for batch operations +// +// Stablecoin handling: +// - If the symbol starts with "$" (e.g., "$1.0"), it's treated as a stablecoin with fixed price +// - Example: "3:$1.0" means token ID 3 is a stablecoin pegged to $1.0 USD func (b *BitgetSDKPriceFeed) GetTokenPrice(ctx context.Context, tokenID uint16) (*TokenPrice, error) { b.mu.RLock() symbol, exists := b.tokenMap[tokenID] @@ -73,23 +82,46 @@ func (b *BitgetSDKPriceFeed) GetTokenPrice(ctx context.Context, tokenID uint16) return nil, fmt.Errorf("token ID %d not mapped to trading pair", tokenID) } - // Fetch token price - tokenPrice, err := b.fetchPrice(ctx, symbol) - if err != nil { - return nil, fmt.Errorf("failed to fetch price for %s: %w", symbol, err) - } - // Use cached ETH price (should be updated by GetBatchTokenPrices) if ethPrice.Cmp(big.NewFloat(0)) == 0 { return nil, fmt.Errorf("ETH price not initialized, please call GetBatchTokenPrices first") } - b.log.Info("Fetched price from Bitget", - "source", "bitget", - "token_id", tokenID, - "symbol", symbol, - "token_price_usd", tokenPrice.String(), - "eth_price_usd", ethPrice.String()) + var tokenPrice *big.Float + + // Check if this is a stablecoin with fixed price (e.g., "$1.0") + if strings.HasPrefix(symbol, StablecoinPrefix) { + priceStr := strings.TrimPrefix(symbol, StablecoinPrefix) + fixedPrice, err := strconv.ParseFloat(priceStr, 64) + if err != nil { + return nil, fmt.Errorf("invalid stablecoin price format '%s': %w", symbol, err) + } + if fixedPrice <= 0 { + return nil, fmt.Errorf("stablecoin price must be positive, got '%s'", symbol) + } + tokenPrice = big.NewFloat(fixedPrice) + + b.log.Info("Using fixed stablecoin price", + "source", "stablecoin", + "token_id", tokenID, + "symbol", symbol, + "token_price_usd", tokenPrice.String(), + "eth_price_usd", ethPrice.String()) + } else { + // Fetch token price from exchange + var err error + tokenPrice, err = b.fetchPrice(ctx, symbol) + if err != nil { + return nil, fmt.Errorf("failed to fetch price for %s: %w", symbol, err) + } + + b.log.Info("Fetched price from Bitget", + "source", "bitget", + "token_id", tokenID, + "symbol", symbol, + "token_price_usd", tokenPrice.String(), + "eth_price_usd", ethPrice.String()) + } return &TokenPrice{ TokenID: tokenID, diff --git a/token-price-oracle/env.example b/token-price-oracle/env.example index ec081737..aadec144 100644 --- a/token-price-oracle/env.example +++ b/token-price-oracle/env.example @@ -18,10 +18,14 @@ TOKEN_PRICE_ORACLE_PRICE_THRESHOLD=100 # basis points (bps), e.g. 100 means 1% TOKEN_PRICE_ORACLE_PRICE_FEED_PRIORITY=bitget # Token mapping for Bitget (tokenID:tradingPair,tokenID:tradingPair) -TOKEN_PRICE_ORACLE_TOKEN_MAPPING_BITGET=1:BGBUSDT,2:BTCUSDT - -# Token mapping for Binance (optional) -# TOKEN_PRICE_ORACLE_TOKEN_MAPPING_BINANCE=1:BGBUSDT,2:BTCUSDT +# Format: +# - Regular tokens: tokenID:SYMBOL (e.g., 1:BGBUSDT, 2:BTCUSDT) +# - Stablecoins: tokenID:$PRICE (e.g., 3:$1.0 for USDT pegged to $1 USD) +# Example: 1:BGBUSDT,2:BTCUSDT,3:$1.0,4:$0.9999 +TOKEN_PRICE_ORACLE_TOKEN_MAPPING_BITGET=1:BGBUSDT,2:BTCUSDT,3:$1.0 + +# Token mapping for Binance (optional, same format as Bitget) +# TOKEN_PRICE_ORACLE_TOKEN_MAPPING_BINANCE=1:BGBUSDT,2:BTCUSDT,3:$1.0 # API base URLs (optional, defaults provided) TOKEN_PRICE_ORACLE_BITGET_API_BASE_URL=https://api.bitget.com diff --git a/token-price-oracle/go.mod b/token-price-oracle/go.mod index d3df4e6a..2b66ea35 100644 --- a/token-price-oracle/go.mod +++ b/token-price-oracle/go.mod @@ -8,7 +8,7 @@ replace ( ) require ( - github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 + github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 github.com/prometheus/client_golang v1.17.0 github.com/sirupsen/logrus v1.9.3 github.com/urfave/cli v1.22.17 diff --git a/token-price-oracle/go.sum b/token-price-oracle/go.sum index 0a2e91ce..603fb9b1 100644 --- a/token-price-oracle/go.sum +++ b/token-price-oracle/go.sum @@ -143,8 +143,8 @@ github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqky github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 h1:8+qaUTn1/eyS8er4RkibhHMFC/L4IgqIXLtORakBDkI= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 h1:r9eaQDNgjAxsuUchmoCFaAjL1TmUfjAmIlJjAtgUk8U= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= diff --git a/token-price-oracle/local.sh b/token-price-oracle/local.sh index 8de108da..609390ce 100644 --- a/token-price-oracle/local.sh +++ b/token-price-oracle/local.sh @@ -8,7 +8,7 @@ --price-update-interval 30s \ --price-threshold 100 \ --price-feed-priority bitget \ - --token-mapping-bitget "1:BGBUSDT,2:BTCUSDT" \ + --token-mapping-bitget "1:BGBUSDT,2:BTCUSDT,3:\$1.0" \ --bitget-api-base-url https://api.bitget.com \ --log-level info \ --metrics-server-enable @@ -16,3 +16,8 @@ # Price threshold examples (in basis points): # 1 bps = 0.01%, 10 bps = 0.1%, 100 bps = 1%, 500 bps = 5%, 1000 bps = 10% +# Token mapping format: +# - Regular tokens: tokenID:SYMBOL (e.g., 1:BGBUSDT, 2:BTCUSDT) +# - Stablecoins: tokenID:$PRICE (e.g., 3:$1.0 for USDT pegged to $1 USD) +# Note: Use \$ in bash to escape the dollar sign + diff --git a/token-price-oracle/metrics/metrics.go b/token-price-oracle/metrics/metrics.go index 4f42c415..e03b6be3 100644 --- a/token-price-oracle/metrics/metrics.go +++ b/token-price-oracle/metrics/metrics.go @@ -2,6 +2,7 @@ package metrics import ( "net/http" + "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -25,12 +26,45 @@ var ( Help: "Account balance in ETH", }, ) + + // LastSuccessfulUpdateTimestamp records the Unix timestamp of the last successful update cycle + // A successful update includes: prices updated on-chain OR prices skipped (below threshold) + // This helps monitor if the oracle is running normally + LastSuccessfulUpdateTimestamp = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "last_successful_update_timestamp", + Help: "Unix timestamp of the last successful price update cycle (includes both updates and skips)", + }, + ) + + // UpdatesTotal counts total number of successful update cycles + UpdatesTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "updates_total", + Help: "Total number of successful update cycles", + }, + []string{"type"}, // type: "updated" or "skipped" + ) ) // init registers all metrics func init() { prometheus.MustRegister(UpdateErrors) prometheus.MustRegister(AccountBalance) + prometheus.MustRegister(LastSuccessfulUpdateTimestamp) + prometheus.MustRegister(UpdatesTotal) + + // Initialize metrics with default values to avoid nil pointer issues in alerting systems + // Set initial timestamp to current time (program start time) + LastSuccessfulUpdateTimestamp.Set(float64(time.Now().Unix())) + // Initialize counter labels to ensure they exist from the start + // Must call Add(0) to actually create the metric, WithLabelValues alone doesn't create it + UpdatesTotal.WithLabelValues("updated").Add(0) + UpdatesTotal.WithLabelValues("skipped").Add(0) + // Initialize error counter labels + UpdateErrors.WithLabelValues("price").Add(0) + // Note: AccountBalance is NOT initialized here to avoid triggering low balance alerts + // It will be set with the real value on the first update cycle } // StartMetricsServer starts metrics HTTP server diff --git a/token-price-oracle/updater/token_price.go b/token-price-oracle/updater/token_price.go index 2f55d431..baaf240d 100644 --- a/token-price-oracle/updater/token_price.go +++ b/token-price-oracle/updater/token_price.go @@ -111,16 +111,23 @@ func (u *PriceUpdater) update(ctx context.Context) error { return nil } - // Step 1: Fetch new prices from feed (USD prices) - tokenPrices, err := u.priceFeed.GetBatchTokenPrices(ctx, tokenIDs) + // Step 0: Filter out inactive tokens BEFORE fetching prices (to save API calls) + activeTokenIDs, tokenInfoMap := u.filterActiveTokens(ctx, tokenIDs) + if len(activeTokenIDs) == 0 { + log.Warn("No active tokens to update after filtering") + return nil + } + + // Step 1: Fetch new prices from feed (USD prices) - only for active tokens + tokenPrices, err := u.priceFeed.GetBatchTokenPrices(ctx, activeTokenIDs) if err != nil { return fmt.Errorf("failed to fetch token prices: %w", err) } - // Step 2: Calculate price ratios using tokenInfo from contract + // Step 2: Calculate price ratios using pre-fetched tokenInfo (no extra contract calls) newPriceRatios := make(map[uint16]*big.Int) for tokenID, tokenPrice := range tokenPrices { - priceRatio, err := u.calculatePriceRatio(ctx, tokenID, tokenPrice) + priceRatio, err := u.calculatePriceRatioWithInfo(tokenID, tokenPrice, tokenInfoMap[tokenID]) if err != nil { log.Warn("Failed to calculate price ratio, skipping", "token_id", tokenID, @@ -181,12 +188,16 @@ func (u *PriceUpdater) update(ctx context.Context) error { if len(tokenIDsToUpdate) == 0 { log.Debug("No prices need updating (all changes below threshold)") + // Record as successful update cycle (skipped) + metrics.LastSuccessfulUpdateTimestamp.Set(float64(time.Now().Unix())) + metrics.UpdatesTotal.WithLabelValues("skipped").Inc() return nil } log.Info("Updating token prices", "token_count", len(tokenIDsToUpdate), "token_ids", tokenIDsToUpdate, + "active_tokens", len(activeTokenIDs), "total_tokens", len(tokenIDs)) // Step 3: Update prices on L2 @@ -218,6 +229,10 @@ func (u *PriceUpdater) update(ctx context.Context) error { "token_count", len(tokenIDsToUpdate)) // Step 5: Update metrics + // Record as successful update cycle (updated) + metrics.LastSuccessfulUpdateTimestamp.Set(float64(time.Now().Unix())) + metrics.UpdatesTotal.WithLabelValues("updated").Inc() + for i, tokenID := range tokenIDsToUpdate { log.Debug("Price updated", "token_id", tokenID, @@ -227,43 +242,80 @@ func (u *PriceUpdater) update(ctx context.Context) error { return nil } -// calculatePriceRatio calculates the price ratio for a token +// TokenInfo is a cached token info from contract +type TokenInfo struct { + TokenAddress string + Decimals uint8 + Scale *big.Int + IsActive bool +} + +// filterActiveTokens filters out inactive tokens and returns active tokenIDs with their info +// This is called BEFORE fetching prices to save API calls +func (u *PriceUpdater) filterActiveTokens(ctx context.Context, tokenIDs []uint16) ([]uint16, map[uint16]*TokenInfo) { + callOpts := &bind.CallOpts{Context: ctx} + activeTokenIDs := make([]uint16, 0, len(tokenIDs)) + tokenInfoMap := make(map[uint16]*TokenInfo) + + for _, tokenID := range tokenIDs { + tokenInfo, err := u.registryContract.GetTokenInfo(callOpts, tokenID) + if err != nil { + log.Warn("Failed to get token info, skipping token", + "token_id", tokenID, + "error", err) + continue + } + + // Log and skip inactive tokens + if !tokenInfo.IsActive { + log.Info("Token is inactive, skipping price update", + "token_id", tokenID, + "address", tokenInfo.TokenAddress.Hex()) + continue + } + + // Cache token info for later use + tokenInfoMap[tokenID] = &TokenInfo{ + TokenAddress: tokenInfo.TokenAddress.Hex(), + Decimals: tokenInfo.Decimals, + Scale: tokenInfo.Scale, + IsActive: tokenInfo.IsActive, + } + activeTokenIDs = append(activeTokenIDs, tokenID) + + log.Debug("Token is active", + "token_id", tokenID, + "address", tokenInfo.TokenAddress.Hex(), + "decimals", tokenInfo.Decimals, + "scale", tokenInfo.Scale.String()) + } + + if len(activeTokenIDs) < len(tokenIDs) { + log.Info("Filtered tokens by active status", + "total", len(tokenIDs), + "active", len(activeTokenIDs), + "skipped", len(tokenIDs)-len(activeTokenIDs)) + } + + return activeTokenIDs, tokenInfoMap +} + +// calculatePriceRatioWithInfo calculates the price ratio using pre-fetched token info // Formula: priceRatio = tokenScale * tokenPriceUSD * 10^(18 - tokenDecimals) / ethPriceUSD // We do multiplications first, then division at the end to avoid precision loss -func (u *PriceUpdater) calculatePriceRatio(ctx context.Context, tokenID uint16, tokenPrice *client.TokenPrice) (*big.Int, error) { +func (u *PriceUpdater) calculatePriceRatioWithInfo(tokenID uint16, tokenPrice *client.TokenPrice, tokenInfo *TokenInfo) (*big.Int, error) { // Validate input price data to prevent nil pointer panics if tokenPrice == nil || tokenPrice.TokenPriceUSD == nil || tokenPrice.EthPriceUSD == nil { return nil, fmt.Errorf("token price data missing for token %d", tokenID) } - // Fetch token info from contract - tokenInfo, err := u.registryContract.GetTokenInfo(&bind.CallOpts{ - Context: ctx, - }, tokenID) - if err != nil { - return nil, fmt.Errorf("failed to get token info from contract: %w", err) - } - - // Check if token is active - if !tokenInfo.IsActive { - return nil, fmt.Errorf("token %d is not active", tokenID) + if tokenInfo == nil { + return nil, fmt.Errorf("token info missing for token %d", tokenID) } tokenScale := tokenInfo.Scale tokenDecimals := tokenInfo.Decimals - log.Debug("Token info from contract", - "token_id", tokenID, - "address", tokenInfo.TokenAddress.Hex(), - "decimals", tokenDecimals, - "token_scale", tokenScale.String(), - "active", tokenInfo.IsActive) - - // Validate token decimals (must be <= 18 for our formula to work) - if tokenDecimals > 18 { - return nil, fmt.Errorf("unsupported token decimals %d (>18) for token %d", tokenDecimals, tokenID) - } - // Check ETH price is not zero if tokenPrice.EthPriceUSD.Cmp(big.NewFloat(0)) == 0 { return nil, fmt.Errorf("ETH price is zero") diff --git a/tx-submitter/go.mod b/tx-submitter/go.mod index aad78cb4..a20c552b 100644 --- a/tx-submitter/go.mod +++ b/tx-submitter/go.mod @@ -9,7 +9,7 @@ require ( github.com/crate-crypto/go-eth-kzg v1.4.0 github.com/holiman/uint256 v1.2.4 github.com/morph-l2/externalsign v0.3.1 - github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 + github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 github.com/prometheus/client_golang v1.17.0 github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a diff --git a/tx-submitter/go.sum b/tx-submitter/go.sum index e5cecfea..b9f9b43b 100644 --- a/tx-submitter/go.sum +++ b/tx-submitter/go.sum @@ -163,8 +163,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morph-l2/externalsign v0.3.1 h1:UYFDZFB0L85A4rDvuwLNBiGEi0kSmg9AZ2v8Q5O4dQo= github.com/morph-l2/externalsign v0.3.1/go.mod h1:b6NJ4GUiiG/gcSJsp3p8ExsIs4ZdphlrVALASnVoGJE= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53 h1:8+qaUTn1/eyS8er4RkibhHMFC/L4IgqIXLtORakBDkI= -github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24 h1:r9eaQDNgjAxsuUchmoCFaAjL1TmUfjAmIlJjAtgUk8U= +github.com/morph-l2/go-ethereum v1.10.14-0.20251203083507-49fa27bcab24/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= github.com/morph-l2/tendermint v0.3.2 h1:Gu6Uj2G6c3YP2NAKFi7A46JZaOCdD4zfZDKCjt0pDm8= github.com/morph-l2/tendermint v0.3.2/go.mod h1:TtCzp9l6Z6yDUiwv3TbqKqw8Q8RKp3fSz5+adO1/Y8w= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=