[1520] Retrieve the feeUSD from coingecko in tx-tracker [WIP] (#1541)
* start implementing retrieving the feeUSD from coingecko * add missing pricesApi in the initialization of tx-tracker service * add missing initialization * add missing cfg for coingecko properties * fix solana in url * replace coingecko api call with notional redis client * add native token list in commons * add decimal places * change feeUSD to Raw.GasPrice * add check for mainnet * replace with sdk const for more coupling * replace pricesApi with notionalCache * undo changes on pricesApi * undo more stuff * remove lines * remove unused timestamp * remove unused props * remove * fix indent * fix import * change * add cache configs * change token_address to native * fixes * fix BNB coingeckoID * fix polygon coingecko_id * change to lowercase * adjust type FeeDoc in operations endpoint * change Blast native token * pr review changes * code-review fixes * comment wormchain * rename var * add comment to wormchain gas token map entry
This commit is contained in:
parent
a55451968f
commit
6ab6824d82
|
@ -76,7 +76,10 @@ type AttributeDoc struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type FeeDoc struct {
|
type FeeDoc struct {
|
||||||
Fee string `bson:"fee" json:"fee"`
|
Fee string `bson:"fee" json:"fee"`
|
||||||
|
RawFee map[string]any `bson:"rawFee" json:"rawFee"`
|
||||||
|
GasTokenNotional string `bson:"gasTokenNotional" json:"gasTokenNotional"`
|
||||||
|
FeeUSD string `bson:"feeUSD" json:"feeUSD"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DestinationTx represents a destination transaction.
|
// DestinationTx represents a destination transaction.
|
||||||
|
|
|
@ -31,6 +31,8 @@ var (
|
||||||
// NFT Bridge
|
// NFT Bridge
|
||||||
"0000000000000000000000000000000000000000000000000000000000000005": "0x1bdffae984043833ed7fe223f7af7a3f8902d04129b14f801823e64827da7130",
|
"0000000000000000000000000000000000000000000000000000000000000005": "0x1bdffae984043833ed7fe223f7af7a3f8902d04129b14f801823e64827da7130",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gasTokenList = GasTokenList()
|
||||||
)
|
)
|
||||||
|
|
||||||
var allChainIDs = make(map[sdk.ChainID]bool)
|
var allChainIDs = make(map[sdk.ChainID]bool)
|
||||||
|
@ -399,3 +401,12 @@ func encodeBech32(hrp string, data []byte) (string, error) {
|
||||||
|
|
||||||
return bech32.Encode(hrp, aligned)
|
return bech32.Encode(hrp, aligned)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetGasTokenMetadata(chainID sdk.ChainID) *TokenMetadata {
|
||||||
|
for i := 0; i < len(gasTokenList); i++ {
|
||||||
|
if gasTokenList[i].TokenChain == chainID {
|
||||||
|
return &gasTokenList[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
|
import sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
|
|
||||||
// manualMainnetTokenList returns a list of tokens that are not generated automatically.
|
// manualMainnetTokenList returns a list of tokens that are not generated automatically.
|
||||||
func manualMainnetTokenList() []TokenMetadata {
|
func manualMainnetTokenList() []TokenMetadata {
|
||||||
return []TokenMetadata{
|
return []TokenMetadata{
|
||||||
|
@ -32,9 +34,62 @@ func manualMainnetTokenList() []TokenMetadata {
|
||||||
// mainnetTokenList returns a list of all tokens on the mainnet.
|
// mainnetTokenList returns a list of all tokens on the mainnet.
|
||||||
func mainnetTokenList() []TokenMetadata {
|
func mainnetTokenList() []TokenMetadata {
|
||||||
res := append(generatedMainnetTokenList(), manualMainnetTokenList()...)
|
res := append(generatedMainnetTokenList(), manualMainnetTokenList()...)
|
||||||
|
res = append(res, GasTokenList()...)
|
||||||
return append(res, unknownTokenList()...)
|
return append(res, unknownTokenList()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GasTokenList : gas tokens are the ones used to pay gas fees on the respective chains, they don't belong to a contract address.
|
||||||
|
func GasTokenList() []TokenMetadata {
|
||||||
|
const nativeTokenAddress = "0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
return []TokenMetadata{
|
||||||
|
{TokenChain: sdk.ChainIDSolana, TokenAddress: nativeTokenAddress, Symbol: "SOL", CoingeckoID: "solana", Decimals: 9},
|
||||||
|
{TokenChain: sdk.ChainIDEthereum, TokenAddress: nativeTokenAddress, Symbol: "ETH", CoingeckoID: "ethereum", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDTerra, TokenAddress: nativeTokenAddress, Symbol: "LUNA", CoingeckoID: "terra-luna", Decimals: 6},
|
||||||
|
{TokenChain: sdk.ChainIDBSC, TokenAddress: nativeTokenAddress, Symbol: "BNB", CoingeckoID: "binancecoin", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDPolygon, TokenAddress: nativeTokenAddress, Symbol: "MATIC", CoingeckoID: "matic-network", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDAvalanche, TokenAddress: nativeTokenAddress, Symbol: "AVAX", CoingeckoID: "avalanche-2", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDOasis, TokenAddress: nativeTokenAddress, Symbol: "ROSE", CoingeckoID: "oasis-network", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDAlgorand, TokenAddress: nativeTokenAddress, Symbol: "ALGO", CoingeckoID: "algorand", Decimals: 6},
|
||||||
|
{TokenChain: sdk.ChainIDAurora, TokenAddress: nativeTokenAddress, Symbol: "AOA", CoingeckoID: "aurora", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDFantom, TokenAddress: nativeTokenAddress, Symbol: "FTM", CoingeckoID: "fantom", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDKarura, TokenAddress: nativeTokenAddress, Symbol: "KAR", CoingeckoID: "karura", Decimals: 12},
|
||||||
|
{TokenChain: sdk.ChainIDAcala, TokenAddress: nativeTokenAddress, Symbol: "ACA", CoingeckoID: "acala", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDKlaytn, TokenAddress: nativeTokenAddress, Symbol: "KLAY", CoingeckoID: "klay-token", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDCelo, TokenAddress: nativeTokenAddress, Symbol: "CELO", CoingeckoID: "celo", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDNear, TokenAddress: nativeTokenAddress, Symbol: "NEAR", CoingeckoID: "near", Decimals: 24},
|
||||||
|
{TokenChain: sdk.ChainIDMoonbeam, TokenAddress: nativeTokenAddress, Symbol: "GLMR", CoingeckoID: "moonbeam", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDTerra2, TokenAddress: nativeTokenAddress, Symbol: "LUNA", CoingeckoID: "terra-luna-2", Decimals: 6},
|
||||||
|
{TokenChain: sdk.ChainIDInjective, TokenAddress: nativeTokenAddress, Symbol: "INJ", CoingeckoID: "injective-protocol", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDOsmosis, TokenAddress: nativeTokenAddress, Symbol: "OSMO", CoingeckoID: "osmosis", Decimals: 6},
|
||||||
|
{TokenChain: sdk.ChainIDSui, TokenAddress: nativeTokenAddress, Symbol: "SUI", CoingeckoID: "sui", Decimals: 9},
|
||||||
|
{TokenChain: sdk.ChainIDAptos, TokenAddress: nativeTokenAddress, Symbol: "APT", CoingeckoID: "aptos", Decimals: 8},
|
||||||
|
{TokenChain: sdk.ChainIDArbitrum, TokenAddress: nativeTokenAddress, Symbol: "ARB", CoingeckoID: "arbitrum", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDOptimism, TokenAddress: nativeTokenAddress, Symbol: "OP", CoingeckoID: "optimism", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDGnosis, TokenAddress: nativeTokenAddress, Symbol: "GNO", CoingeckoID: "gnosis", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDXpla, TokenAddress: nativeTokenAddress, Symbol: "XPLA", CoingeckoID: "xpla", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDBtc, TokenAddress: nativeTokenAddress, Symbol: "BTC", CoingeckoID: "bitcoin", Decimals: 8},
|
||||||
|
{TokenChain: sdk.ChainIDBase, TokenAddress: nativeTokenAddress, Symbol: "ETH", CoingeckoID: "ethereum", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDSei, TokenAddress: nativeTokenAddress, Symbol: "SEI", CoingeckoID: "sei-network", Decimals: 6},
|
||||||
|
{TokenChain: sdk.ChainIDRootstock, TokenAddress: nativeTokenAddress, Symbol: "RSK", CoingeckoID: "rootstock", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDScroll, TokenAddress: nativeTokenAddress, Symbol: "ETH", CoingeckoID: "ethereum", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDMantle, TokenAddress: nativeTokenAddress, Symbol: "MNT", CoingeckoID: "mantle", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDBlast, TokenAddress: nativeTokenAddress, Symbol: "ETH", CoingeckoID: "ethereum", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDXLayer, TokenAddress: nativeTokenAddress, Symbol: "XLYR", CoingeckoID: "xlayer", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDLinea, TokenAddress: nativeTokenAddress, Symbol: "ETH", CoingeckoID: "ethereum", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDBerachain, TokenAddress: nativeTokenAddress, Symbol: "BERA", CoingeckoID: "berachain-bera", Decimals: 18},
|
||||||
|
//{TokenChain: sdk.ChainIDWormchain, TokenAddress: nativeTokenAddress, Symbol: "WORM", CoingeckoID: "wormchain", Decimals: 18}, // Currently Wormchain doesn't charge gas fees to wormhole messages. This may change in the future: https://docs.wormhole.com/wormhole/explore-wormhole/gateway
|
||||||
|
{TokenChain: sdk.ChainIDCosmoshub, TokenAddress: nativeTokenAddress, Symbol: "ATOM", CoingeckoID: "cosmos", Decimals: 6},
|
||||||
|
{TokenChain: sdk.ChainIDEvmos, TokenAddress: nativeTokenAddress, Symbol: "EVMOS", CoingeckoID: "evmos", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDKujira, TokenAddress: nativeTokenAddress, Symbol: "KUJI", CoingeckoID: "kujira", Decimals: 6},
|
||||||
|
{TokenChain: sdk.ChainIDNeutron, TokenAddress: nativeTokenAddress, Symbol: "NEUT", CoingeckoID: "neutron-3", Decimals: 6},
|
||||||
|
{TokenChain: sdk.ChainIDCelestia, TokenAddress: nativeTokenAddress, Symbol: "TIA", CoingeckoID: "celestia", Decimals: 6},
|
||||||
|
{TokenChain: sdk.ChainIDStargaze, TokenAddress: nativeTokenAddress, Symbol: "STARS", CoingeckoID: "stargaze", Decimals: 6},
|
||||||
|
{TokenChain: sdk.ChainIDSeda, TokenAddress: nativeTokenAddress, Symbol: "SEDA", CoingeckoID: "seda-2", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDDymension, TokenAddress: nativeTokenAddress, Symbol: "DYM", CoingeckoID: "dymension", Decimals: 18},
|
||||||
|
{TokenChain: sdk.ChainIDProvenance, TokenAddress: nativeTokenAddress, Symbol: "HASH", CoingeckoID: "provenance-blockchain", Decimals: 9},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func unknownTokenList() []TokenMetadata {
|
func unknownTokenList() []TokenMetadata {
|
||||||
return []TokenMetadata{
|
return []TokenMetadata{
|
||||||
{TokenChain: 23, TokenAddress: "00000000000000000000000007dd5beaffb65b8ff2e575d500bdf324a05295dc", Symbol: "arbi", CoingeckoID: "arbipad", Decimals: 18},
|
{TokenChain: 23, TokenAddress: "00000000000000000000000007dd5beaffb65b8ff2e575d500bdf324a05295dc", Symbol: "arbi", CoingeckoID: "arbipad", Decimals: 18},
|
||||||
|
|
|
@ -17,6 +17,7 @@ SQS_AWS_REGION=
|
||||||
P2P_NETWORK=mainnet
|
P2P_NETWORK=mainnet
|
||||||
AWS_IAM_ROLE=
|
AWS_IAM_ROLE=
|
||||||
METRICS_ENABLED=true
|
METRICS_ENABLED=true
|
||||||
|
NOTIONAL_CACHE_CHANNEL=WORMSCAN:NOTIONAL
|
||||||
|
|
||||||
ACALA_BASE_URL=https://eth-rpc-acala.aca-api.network
|
ACALA_BASE_URL=https://eth-rpc-acala.aca-api.network
|
||||||
ACALA_REQUESTS_PER_MINUTE=12
|
ACALA_REQUESTS_PER_MINUTE=12
|
||||||
|
|
|
@ -17,6 +17,7 @@ SQS_AWS_REGION=
|
||||||
P2P_NETWORK=testnet
|
P2P_NETWORK=testnet
|
||||||
AWS_IAM_ROLE=
|
AWS_IAM_ROLE=
|
||||||
METRICS_ENABLED=true
|
METRICS_ENABLED=true
|
||||||
|
NOTIONAL_CACHE_CHANNEL=WORMSCAN:NOTIONAL
|
||||||
|
|
||||||
ACALA_BASE_URL=https://acala-dev.aca-dev.network/eth/http
|
ACALA_BASE_URL=https://acala-dev.aca-dev.network/eth/http
|
||||||
ACALA_REQUESTS_PER_MINUTE=12
|
ACALA_REQUESTS_PER_MINUTE=12
|
||||||
|
|
|
@ -17,6 +17,7 @@ SQS_AWS_REGION=
|
||||||
P2P_NETWORK=mainnet
|
P2P_NETWORK=mainnet
|
||||||
AWS_IAM_ROLE=
|
AWS_IAM_ROLE=
|
||||||
METRICS_ENABLED=true
|
METRICS_ENABLED=true
|
||||||
|
NOTIONAL_CACHE_CHANNEL=WORMSCAN:NOTIONAL
|
||||||
|
|
||||||
ACALA_BASE_URL=https://eth-rpc-acala.aca-api.network
|
ACALA_BASE_URL=https://eth-rpc-acala.aca-api.network
|
||||||
ACALA_REQUESTS_PER_MINUTE=12
|
ACALA_REQUESTS_PER_MINUTE=12
|
||||||
|
|
|
@ -17,6 +17,7 @@ SQS_AWS_REGION=
|
||||||
P2P_NETWORK=testnet
|
P2P_NETWORK=testnet
|
||||||
AWS_IAM_ROLE=
|
AWS_IAM_ROLE=
|
||||||
METRICS_ENABLED=true
|
METRICS_ENABLED=true
|
||||||
|
NOTIONAL_CACHE_CHANNEL=WORMSCAN:NOTIONAL
|
||||||
|
|
||||||
ACALA_BASE_URL=https://acala-dev.aca-dev.network/eth/http
|
ACALA_BASE_URL=https://acala-dev.aca-dev.network/eth/http
|
||||||
ACALA_REQUESTS_PER_MINUTE=12
|
ACALA_REQUESTS_PER_MINUTE=12
|
||||||
|
|
|
@ -75,6 +75,18 @@ spec:
|
||||||
value: "/opt/tx-tracker/rpc-provider.json"
|
value: "/opt/tx-tracker/rpc-provider.json"
|
||||||
- name: CONSUMER_WORKERS_SIZE
|
- name: CONSUMER_WORKERS_SIZE
|
||||||
value: "1"
|
value: "1"
|
||||||
|
- name: NOTIONAL_CACHE_CHANNEL
|
||||||
|
value: {{ .NOTIONAL_CACHE_CHANNEL }}
|
||||||
|
- name: NOTIONAL_CACHE_URL
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: config
|
||||||
|
key: redis-uri
|
||||||
|
- name: NOTIONAL_CACHE_PREFIX
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: config
|
||||||
|
key: redis-prefix
|
||||||
image: {{ .IMAGE_NAME }}
|
image: {{ .IMAGE_NAME }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
|
|
|
@ -3,14 +3,15 @@ package chains
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/client/cache/notional"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/pool"
|
"github.com/wormhole-foundation/wormhole-explorer/common/pool"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/txtracker/internal/metrics"
|
"github.com/wormhole-foundation/wormhole-explorer/txtracker/internal/metrics"
|
||||||
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -35,7 +36,9 @@ type ethGetTransactionReceiptResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type apiEvm struct {
|
type apiEvm struct {
|
||||||
chainId sdk.ChainID
|
chainId sdk.ChainID
|
||||||
|
notionalCache *notional.NotionalCache
|
||||||
|
p2pNetwork string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *apiEvm) FetchEvmTx(
|
func (e *apiEvm) FetchEvmTx(
|
||||||
|
@ -75,12 +78,23 @@ func (e *apiEvm) FetchEvmTx(
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.String("txHash", txHash),
|
zap.String("txHash", txHash),
|
||||||
zap.String("chainId", e.chainId.String()))
|
zap.String("chainId", e.chainId.String()))
|
||||||
} else if fee == "" {
|
} else if fee == nil {
|
||||||
txDetail.FeeDetail = nil
|
txDetail.FeeDetail = nil
|
||||||
} else {
|
} else {
|
||||||
txDetail.FeeDetail.Fee = fee
|
txDetail.FeeDetail.Fee = fee.String()
|
||||||
|
if e.p2pNetwork == domain.P2pMainNet {
|
||||||
|
gasPrice, errGasPrice := GetGasTokenNotional(e.chainId, e.notionalCache)
|
||||||
|
if errGasPrice != nil {
|
||||||
|
logger.Error("Failed to get gas price",
|
||||||
|
zap.Error(errGasPrice),
|
||||||
|
zap.String("chainId", e.chainId.String()),
|
||||||
|
zap.String("txHash", txHash))
|
||||||
|
} else {
|
||||||
|
txDetail.FeeDetail.GasTokenNotional = gasPrice.NotionalUsd.String()
|
||||||
|
txDetail.FeeDetail.FeeUSD = gasPrice.NotionalUsd.Mul(*fee).String()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return txDetail, err
|
return txDetail, err
|
||||||
|
@ -176,17 +190,17 @@ func (e *apiEvm) fetchEvmTxReceiptByTxHash(
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func EvmCalculateFee(chainID sdk.ChainID, gasUsed string, effectiveGasPrice string) (string, error) {
|
func EvmCalculateFee(chainID sdk.ChainID, gasUsed string, effectiveGasPrice string) (*decimal.Decimal, error) {
|
||||||
//ignore if the blockchain is L2
|
//ignore if the blockchain is L2
|
||||||
if chainID == sdk.ChainIDBase || chainID == sdk.ChainIDOptimism || chainID == sdk.ChainIDScroll {
|
if chainID == sdk.ChainIDBase || chainID == sdk.ChainIDOptimism || chainID == sdk.ChainIDScroll {
|
||||||
return "", nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// get decimal gasUsed
|
// get decimal gasUsed
|
||||||
gs := new(big.Int)
|
gs := new(big.Int)
|
||||||
_, ok := gs.SetString(gasUsed, 0)
|
_, ok := gs.SetString(gasUsed, 0)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("failed to convert gasUsed to big.Int")
|
return nil, fmt.Errorf("failed to convert gasUsed to big.Int")
|
||||||
}
|
}
|
||||||
decimalGasUsed := decimal.NewFromBigInt(gs, 0)
|
decimalGasUsed := decimal.NewFromBigInt(gs, 0)
|
||||||
|
|
||||||
|
@ -194,12 +208,12 @@ func EvmCalculateFee(chainID sdk.ChainID, gasUsed string, effectiveGasPrice stri
|
||||||
gp := new(big.Int)
|
gp := new(big.Int)
|
||||||
_, ok = gp.SetString(effectiveGasPrice, 0)
|
_, ok = gp.SetString(effectiveGasPrice, 0)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("failed to convert gasPrice to big.Int")
|
return nil, fmt.Errorf("failed to convert gasPrice to big.Int")
|
||||||
}
|
}
|
||||||
decimalGasPrice := decimal.NewFromBigInt(gp, 0)
|
decimalGasPrice := decimal.NewFromBigInt(gp, 0)
|
||||||
|
|
||||||
// calculate gasUsed * (gasPrice / 1e18)
|
// calculate gasUsed * (gasPrice / 1e18)
|
||||||
decimalFee := decimalGasUsed.Mul(decimalGasPrice)
|
decimalFee := decimalGasUsed.Mul(decimalGasPrice)
|
||||||
decimalFee = decimalFee.DivRound(decimal.NewFromInt(1e18), 18)
|
decimalFee = decimalFee.DivRound(decimal.NewFromInt(1e18), 18)
|
||||||
return decimalFee.String(), nil
|
return &decimalFee, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
notional "github.com/wormhole-foundation/wormhole-explorer/common/client/cache/notional"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mr-tron/base58"
|
"github.com/mr-tron/base58"
|
||||||
|
@ -58,7 +60,9 @@ type getTransactionConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type apiSolana struct {
|
type apiSolana struct {
|
||||||
timestamp *time.Time
|
timestamp *time.Time
|
||||||
|
notionalCache *notional.NotionalCache
|
||||||
|
p2pNetwork string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *apiSolana) FetchSolanaTx(
|
func (a *apiSolana) FetchSolanaTx(
|
||||||
|
@ -92,6 +96,16 @@ func (a *apiSolana) FetchSolanaTx(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if txDetail.FeeDetail != nil && txDetail.FeeDetail.Fee != "" && a.p2pNetwork == domain.P2pMainNet {
|
||||||
|
gasPrice, errGasPrice := GetGasTokenNotional(sdk.ChainIDSolana, a.notionalCache)
|
||||||
|
if errGasPrice != nil {
|
||||||
|
logger.Error("Failed to get gas price", zap.Error(errGasPrice), zap.String("chainId", sdk.ChainIDSolana.String()), zap.String("txHash", txHash))
|
||||||
|
} else {
|
||||||
|
txDetail.FeeDetail.GasTokenNotional = gasPrice.NotionalUsd.String()
|
||||||
|
txDetail.FeeDetail.FeeUSD = gasPrice.NotionalUsd.Mul(decimal.RequireFromString(txDetail.FeeDetail.Fee)).String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return txDetail, err
|
return txDetail, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,15 +210,15 @@ func (a *apiSolana) fetchSolanaTx(
|
||||||
"fee": fmt.Sprintf("%d", *response.Meta.Fee),
|
"fee": fmt.Sprintf("%d", *response.Meta.Fee),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
feeDetail.Fee = SolanaCalculateFee(*response.Meta.Fee)
|
feeDetail.Fee = SolanaCalculateFee(*response.Meta.Fee).String()
|
||||||
txDetail.FeeDetail = feeDetail
|
txDetail.FeeDetail = feeDetail
|
||||||
}
|
}
|
||||||
|
|
||||||
return &txDetail, nil
|
return &txDetail, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SolanaCalculateFee(fee uint64) string {
|
func SolanaCalculateFee(fee uint64) decimal.Decimal {
|
||||||
rawFee := decimal.NewFromUint64(fee)
|
rawFee := decimal.NewFromUint64(fee)
|
||||||
calculatedFee := rawFee.DivRound(decimal.NewFromInt(1e9), 9)
|
calculatedFee := rawFee.DivRound(decimal.NewFromInt(1e9), 9)
|
||||||
return calculatedFee.String()
|
return calculatedFee
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
notional "github.com/wormhole-foundation/wormhole-explorer/common/client/cache/notional"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/pool"
|
"github.com/wormhole-foundation/wormhole-explorer/common/pool"
|
||||||
|
@ -29,8 +30,10 @@ type TxDetail struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type FeeDetail struct {
|
type FeeDetail struct {
|
||||||
Fee string `bson:"fee" json:"fee"`
|
Fee string `bson:"fee" json:"fee"`
|
||||||
RawFee map[string]string `bson:"rawFee" json:"rawFee"`
|
RawFee map[string]string `bson:"rawFee" json:"rawFee"`
|
||||||
|
GasTokenNotional string `bson:"gasTokenNotional" json:"gasTokenNotional"`
|
||||||
|
FeeUSD string `bson:"feeUSD" json:"feeUSD"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AttributeTxDetail struct {
|
type AttributeTxDetail struct {
|
||||||
|
@ -48,13 +51,16 @@ func FetchTx(
|
||||||
p2pNetwork string,
|
p2pNetwork string,
|
||||||
m metrics.Metrics,
|
m metrics.Metrics,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
|
notionalCache *notional.NotionalCache,
|
||||||
) (*TxDetail, error) {
|
) (*TxDetail, error) {
|
||||||
// Decide which RPC/API service to use based on chain ID
|
// Decide which RPC/API service to use based on chain ID
|
||||||
var fetchFunc func(ctx context.Context, pool *pool.Pool, txHash string, metrics metrics.Metrics, logger *zap.Logger) (*TxDetail, error)
|
var fetchFunc func(ctx context.Context, pool *pool.Pool, txHash string, metrics metrics.Metrics, logger *zap.Logger) (*TxDetail, error)
|
||||||
switch chainId {
|
switch chainId {
|
||||||
case sdk.ChainIDSolana:
|
case sdk.ChainIDSolana:
|
||||||
apiSolana := &apiSolana{
|
apiSolana := &apiSolana{
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
|
notionalCache: notionalCache,
|
||||||
|
p2pNetwork: p2pNetwork,
|
||||||
}
|
}
|
||||||
fetchFunc = apiSolana.FetchSolanaTx
|
fetchFunc = apiSolana.FetchSolanaTx
|
||||||
case sdk.ChainIDAlgorand:
|
case sdk.ChainIDAlgorand:
|
||||||
|
@ -95,7 +101,9 @@ func FetchTx(
|
||||||
sdk.ChainIDMantle,
|
sdk.ChainIDMantle,
|
||||||
sdk.ChainIDPolygonSepolia: // polygon amoy
|
sdk.ChainIDPolygonSepolia: // polygon amoy
|
||||||
apiEvm := &apiEvm{
|
apiEvm := &apiEvm{
|
||||||
chainId: chainId,
|
chainId: chainId,
|
||||||
|
notionalCache: notionalCache,
|
||||||
|
p2pNetwork: p2pNetwork,
|
||||||
}
|
}
|
||||||
fetchFunc = apiEvm.FetchEvmTx
|
fetchFunc = apiEvm.FetchEvmTx
|
||||||
case sdk.ChainIDWormchain:
|
case sdk.ChainIDWormchain:
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/client/cache/notional"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -145,3 +147,11 @@ func FormatTxHashByChain(chainId sdk.ChainID, txHash string) string {
|
||||||
return txHash
|
return txHash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetGasTokenNotional(chainID sdk.ChainID, notionalCache *notional.NotionalCache) (notional.PriceData, error) {
|
||||||
|
nativeToken := domain.GetGasTokenMetadata(chainID)
|
||||||
|
if nativeToken == nil {
|
||||||
|
return notional.PriceData{}, fmt.Errorf("gas token not found for chain %s", chainID)
|
||||||
|
}
|
||||||
|
return notionalCache.Get(nativeToken.GetTokenID())
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package backfiller
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/client/cache/notional"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
@ -103,6 +105,16 @@ func RunByVaas(backfillerConfig *VaasBackfiller) {
|
||||||
// create a consumer repository.
|
// create a consumer repository.
|
||||||
globalTrxRepository := consumer.NewRepository(logger, db.Database)
|
globalTrxRepository := consumer.NewRepository(logger, db.Database)
|
||||||
|
|
||||||
|
redisClient := redis.NewClient(&redis.Options{Addr: cfg.NotionalCacheURL})
|
||||||
|
notionalCache, errCache := notional.NewNotionalCache(ctx, redisClient, cfg.NotionalCachePrefix, cfg.NotionalCacheChannel, logger)
|
||||||
|
if errCache != nil {
|
||||||
|
logger.Fatal("Failed to create notional cache", zap.Error(errCache))
|
||||||
|
}
|
||||||
|
errCache = notionalCache.Init(ctx)
|
||||||
|
if errCache != nil {
|
||||||
|
logger.Fatal("Failed to initialize notional cache", zap.Error(errCache))
|
||||||
|
}
|
||||||
|
|
||||||
query := repository.VaaQuery{
|
query := repository.VaaQuery{
|
||||||
StartTime: &startTime,
|
StartTime: &startTime,
|
||||||
EndTime: &endTime,
|
EndTime: &endTime,
|
||||||
|
@ -142,7 +154,7 @@ func RunByVaas(backfillerConfig *VaasBackfiller) {
|
||||||
processedDocumentsSuccess: &quantityConsumedSuccess,
|
processedDocumentsSuccess: &quantityConsumedSuccess,
|
||||||
processedDocumentsWithError: &quantityConsumedWithError,
|
processedDocumentsWithError: &quantityConsumedWithError,
|
||||||
}
|
}
|
||||||
go processVaa(ctx, &p)
|
go processVaa(ctx, &p, notionalCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("Waiting for all workers to finish...")
|
logger.Info("Waiting for all workers to finish...")
|
||||||
|
@ -195,7 +207,7 @@ func getVaas(ctx context.Context, logger *zap.Logger, pagination repository.Pagi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func processVaa(ctx context.Context, params *vaasBackfillerParams) {
|
func processVaa(ctx context.Context, params *vaasBackfillerParams, cache *notional.NotionalCache) {
|
||||||
// Main loop: fetch global txs and process them
|
// Main loop: fetch global txs and process them
|
||||||
metrics := metrics.NewDummyMetrics()
|
metrics := metrics.NewDummyMetrics()
|
||||||
defer params.wg.Done()
|
defer params.wg.Done()
|
||||||
|
@ -226,7 +238,7 @@ func processVaa(ctx context.Context, params *vaasBackfillerParams) {
|
||||||
Metrics: metrics,
|
Metrics: metrics,
|
||||||
DisableDBUpsert: params.disableDBUpsert,
|
DisableDBUpsert: params.disableDBUpsert,
|
||||||
}
|
}
|
||||||
_, err := consumer.ProcessSourceTx(ctx, params.logger, params.rpcPool, params.wormchainRpcPool, params.repository, &p, params.p2pNetwork)
|
_, err := consumer.ProcessSourceTx(ctx, params.logger, params.rpcPool, params.wormchainRpcPool, params.repository, &p, params.p2pNetwork, cache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, consumer.ErrAlreadyProcessed) {
|
if errors.Is(err, consumer.ErrAlreadyProcessed) {
|
||||||
params.logger.Info("Source tx was already processed", zap.String("vaaId", v.ID))
|
params.logger.Info("Source tx was already processed", zap.String("vaaId", v.ID))
|
||||||
|
|
|
@ -3,6 +3,8 @@ package service
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/client/cache/notional"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
@ -64,8 +66,18 @@ func Run() {
|
||||||
repository := consumer.NewRepository(logger, db.Database)
|
repository := consumer.NewRepository(logger, db.Database)
|
||||||
vaaRepository := vaa.NewRepository(db.Database, logger)
|
vaaRepository := vaa.NewRepository(db.Database, logger)
|
||||||
|
|
||||||
|
redisClient := redis.NewClient(&redis.Options{Addr: cfg.NotionalCacheURL})
|
||||||
|
notionalCache, errCache := notional.NewNotionalCache(rootCtx, redisClient, cfg.NotionalCachePrefix, cfg.NotionalCacheChannel, logger)
|
||||||
|
if errCache != nil {
|
||||||
|
logger.Fatal("Failed to create notional cache", zap.Error(errCache))
|
||||||
|
}
|
||||||
|
errCache = notionalCache.Init(rootCtx)
|
||||||
|
if errCache != nil {
|
||||||
|
logger.Fatal("Failed to initialize notional cache", zap.Error(errCache))
|
||||||
|
}
|
||||||
|
|
||||||
// create controller
|
// create controller
|
||||||
vaaController := vaa.NewController(rpcPool, wormchainRpcPool, vaaRepository, repository, cfg.P2pNetwork, logger)
|
vaaController := vaa.NewController(rpcPool, wormchainRpcPool, vaaRepository, repository, cfg.P2pNetwork, logger, notionalCache)
|
||||||
|
|
||||||
// start serving /health and /ready endpoints
|
// start serving /health and /ready endpoints
|
||||||
healthChecks, err := makeHealthChecks(rootCtx, cfg, db.Database)
|
healthChecks, err := makeHealthChecks(rootCtx, cfg, db.Database)
|
||||||
|
@ -77,12 +89,12 @@ func Run() {
|
||||||
|
|
||||||
// create and start a pipeline consumer.
|
// create and start a pipeline consumer.
|
||||||
vaaConsumeFunc := newVAAConsumeFunc(rootCtx, cfg, metrics, logger)
|
vaaConsumeFunc := newVAAConsumeFunc(rootCtx, cfg, metrics, logger)
|
||||||
vaaConsumer := consumer.New(vaaConsumeFunc, rpcPool, wormchainRpcPool, rootCtx, logger, repository, metrics, cfg.P2pNetwork, cfg.ConsumerWorkersSize)
|
vaaConsumer := consumer.New(vaaConsumeFunc, rpcPool, wormchainRpcPool, logger, repository, metrics, cfg.P2pNetwork, cfg.ConsumerWorkersSize, notionalCache)
|
||||||
vaaConsumer.Start(rootCtx)
|
vaaConsumer.Start(rootCtx)
|
||||||
|
|
||||||
// create and start a notification consumer.
|
// create and start a notification consumer.
|
||||||
notificationConsumeFunc := newNotificationConsumeFunc(rootCtx, cfg, metrics, logger)
|
notificationConsumeFunc := newNotificationConsumeFunc(rootCtx, cfg, metrics, logger)
|
||||||
notificationConsumer := consumer.New(notificationConsumeFunc, rpcPool, wormchainRpcPool, rootCtx, logger, repository, metrics, cfg.P2pNetwork, cfg.ConsumerWorkersSize)
|
notificationConsumer := consumer.New(notificationConsumeFunc, rpcPool, wormchainRpcPool, logger, repository, metrics, cfg.P2pNetwork, cfg.ConsumerWorkersSize, notionalCache)
|
||||||
notificationConsumer.Start(rootCtx)
|
notificationConsumer.Start(rootCtx)
|
||||||
|
|
||||||
logger.Info("Started wormhole-explorer-tx-tracker")
|
logger.Info("Started wormhole-explorer-tx-tracker")
|
||||||
|
|
|
@ -16,14 +16,17 @@ import (
|
||||||
|
|
||||||
type ServiceSettings struct {
|
type ServiceSettings struct {
|
||||||
// MonitoringPort defines the TCP port for the /health and /ready endpoints.
|
// MonitoringPort defines the TCP port for the /health and /ready endpoints.
|
||||||
MonitoringPort string `split_words:"true" default:"8000"`
|
MonitoringPort string `split_words:"true" default:"8000"`
|
||||||
Environment string `split_words:"true" required:"true"`
|
Environment string `split_words:"true" required:"true"`
|
||||||
LogLevel string `split_words:"true" default:"INFO"`
|
LogLevel string `split_words:"true" default:"INFO"`
|
||||||
PprofEnabled bool `split_words:"true" default:"false"`
|
PprofEnabled bool `split_words:"true" default:"false"`
|
||||||
MetricsEnabled bool `split_words:"true" default:"false"`
|
MetricsEnabled bool `split_words:"true" default:"false"`
|
||||||
P2pNetwork string `split_words:"true" required:"true"`
|
P2pNetwork string `split_words:"true" required:"true"`
|
||||||
RpcProviderPath string `split_words:"true" required:"false"`
|
RpcProviderPath string `split_words:"true" required:"false"`
|
||||||
ConsumerWorkersSize int `split_words:"true" default:"10"`
|
ConsumerWorkersSize int `split_words:"true" default:"10"`
|
||||||
|
NotionalCacheURL string `split_words:"true" required:"true"`
|
||||||
|
NotionalCachePrefix string `split_words:"true" required:"true"`
|
||||||
|
NotionalCacheChannel string `split_words:"true" required:"true"`
|
||||||
AwsSettings
|
AwsSettings
|
||||||
MongodbSettings
|
MongodbSettings
|
||||||
*RpcProviderSettings `required:"false"`
|
*RpcProviderSettings `required:"false"`
|
||||||
|
@ -35,6 +38,9 @@ type ServiceSettings struct {
|
||||||
type RpcProviderSettingsJson struct {
|
type RpcProviderSettingsJson struct {
|
||||||
RpcProviders []ChainRpcProviderSettings `json:"rpcProviders"`
|
RpcProviders []ChainRpcProviderSettings `json:"rpcProviders"`
|
||||||
WormchainRpcProviders []ChainRpcProviderSettings `json:"wormchainRpcProviders"`
|
WormchainRpcProviders []ChainRpcProviderSettings `json:"wormchainRpcProviders"`
|
||||||
|
NotionalCacheURL string `json:"notional_cache_url"`
|
||||||
|
NotionalCachePrefix string `json:"notional_cache_prefix"`
|
||||||
|
NotionalCacheChannel string `json:"notional_cache_channel"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChainRpcProviderSettings struct {
|
type ChainRpcProviderSettings struct {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package consumer
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/client/cache/notional"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/pool"
|
"github.com/wormhole-foundation/wormhole-explorer/common/pool"
|
||||||
|
@ -24,19 +25,19 @@ type Consumer struct {
|
||||||
metrics metrics.Metrics
|
metrics metrics.Metrics
|
||||||
p2pNetwork string
|
p2pNetwork string
|
||||||
workersSize int
|
workersSize int
|
||||||
|
notionalCache *notional.NotionalCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new vaa consumer.
|
// New creates a new vaa consumer.
|
||||||
func New(
|
func New(consumeFunc queue.ConsumeFunc,
|
||||||
consumeFunc queue.ConsumeFunc,
|
|
||||||
rpcPool map[vaa.ChainID]*pool.Pool,
|
rpcPool map[vaa.ChainID]*pool.Pool,
|
||||||
wormchainRpcPool map[vaa.ChainID]*pool.Pool,
|
wormchainRpcPool map[vaa.ChainID]*pool.Pool,
|
||||||
ctx context.Context,
|
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
repository *Repository,
|
repository *Repository,
|
||||||
metrics metrics.Metrics,
|
metrics metrics.Metrics,
|
||||||
p2pNetwork string,
|
p2pNetwork string,
|
||||||
workersSize int,
|
workersSize int,
|
||||||
|
notionalCache *notional.NotionalCache,
|
||||||
) *Consumer {
|
) *Consumer {
|
||||||
|
|
||||||
c := Consumer{
|
c := Consumer{
|
||||||
|
@ -48,6 +49,7 @@ func New(
|
||||||
metrics: metrics,
|
metrics: metrics,
|
||||||
p2pNetwork: p2pNetwork,
|
p2pNetwork: p2pNetwork,
|
||||||
workersSize: workersSize,
|
workersSize: workersSize,
|
||||||
|
notionalCache: notionalCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &c
|
return &c
|
||||||
|
@ -118,7 +120,7 @@ func (c *Consumer) processSourceTx(ctx context.Context, msg queue.ConsumerMessag
|
||||||
Source: event.Source,
|
Source: event.Source,
|
||||||
SentTimestamp: msg.SentTimestamp(),
|
SentTimestamp: msg.SentTimestamp(),
|
||||||
}
|
}
|
||||||
_, err := ProcessSourceTx(ctx, c.logger, c.rpcpool, c.wormchainRpcPool, c.repository, &p, c.p2pNetwork)
|
_, err := ProcessSourceTx(ctx, c.logger, c.rpcpool, c.wormchainRpcPool, c.repository, &p, c.p2pNetwork, c.notionalCache)
|
||||||
|
|
||||||
// add vaa processing duration metrics
|
// add vaa processing duration metrics
|
||||||
c.metrics.AddVaaProcessedDuration(uint16(event.ChainID), time.Since(start).Seconds())
|
c.metrics.AddVaaProcessedDuration(uint16(event.ChainID), time.Since(start).Seconds())
|
||||||
|
@ -204,8 +206,9 @@ func (c *Consumer) processTargetTx(ctx context.Context, msg queue.ConsumerMessag
|
||||||
EvmFee: evmFee,
|
EvmFee: evmFee,
|
||||||
SolanaFee: solanaFee,
|
SolanaFee: solanaFee,
|
||||||
Metrics: c.metrics,
|
Metrics: c.metrics,
|
||||||
|
P2pNetwork: c.p2pNetwork,
|
||||||
}
|
}
|
||||||
err := ProcessTargetTx(ctx, c.logger, c.repository, &p)
|
err := ProcessTargetTx(ctx, c.logger, c.repository, &p, c.notionalCache)
|
||||||
|
|
||||||
elapsedLog := zap.Uint64("elapsedTime", uint64(time.Since(start).Milliseconds()))
|
elapsedLog := zap.Uint64("elapsedTime", uint64(time.Since(start).Milliseconds()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -32,8 +32,10 @@ type DestinationTx struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type FeeDetail struct {
|
type FeeDetail struct {
|
||||||
Fee string `bson:"fee"`
|
Fee string `bson:"fee"`
|
||||||
RawFee map[string]string `bson:"rawFee"`
|
RawFee map[string]string `bson:"rawFee"`
|
||||||
|
GasTokenNotional string `bson:"gasTokenNotional" json:"gasTokenNotional"`
|
||||||
|
FeeUSD string `bson:"feeUSD" json:"feeUSD"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TargetTxUpdate represents a transaction document.
|
// TargetTxUpdate represents a transaction document.
|
||||||
|
|
|
@ -3,6 +3,7 @@ package consumer
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
notionalCache "github.com/wormhole-foundation/wormhole-explorer/common/client/cache/notional"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
|
@ -38,6 +39,7 @@ type ProcessSourceTxParams struct {
|
||||||
Metrics metrics.Metrics
|
Metrics metrics.Metrics
|
||||||
SentTimestamp *time.Time
|
SentTimestamp *time.Time
|
||||||
DisableDBUpsert bool
|
DisableDBUpsert bool
|
||||||
|
P2pNetwork string
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProcessSourceTx(
|
func ProcessSourceTx(
|
||||||
|
@ -48,6 +50,7 @@ func ProcessSourceTx(
|
||||||
repository *Repository,
|
repository *Repository,
|
||||||
params *ProcessSourceTxParams,
|
params *ProcessSourceTxParams,
|
||||||
p2pNetwork string,
|
p2pNetwork string,
|
||||||
|
notionalCache *notionalCache.NotionalCache,
|
||||||
) (*chains.TxDetail, error) {
|
) (*chains.TxDetail, error) {
|
||||||
|
|
||||||
if !params.Overwrite {
|
if !params.Overwrite {
|
||||||
|
@ -114,7 +117,7 @@ func ProcessSourceTx(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get transaction details from the emitter blockchain
|
// Get transaction details from the emitter blockchain
|
||||||
txDetail, err = chains.FetchTx(ctx, rpcPool, wormchainRpcPool, params.ChainId, params.TxHash, params.Timestamp, p2pNetwork, params.Metrics, logger)
|
txDetail, err = chains.FetchTx(ctx, rpcPool, wormchainRpcPool, params.ChainId, params.TxHash, params.Timestamp, p2pNetwork, params.Metrics, logger, notionalCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errHandleFetchTx := handleFetchTxError(ctx, logger, repository, params, err)
|
errHandleFetchTx := handleFetchTxError(ctx, logger, repository, params, err)
|
||||||
if errHandleFetchTx == nil {
|
if errHandleFetchTx == nil {
|
||||||
|
|
|
@ -3,6 +3,8 @@ package consumer
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/client/cache/notional"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -36,6 +38,7 @@ type ProcessTargetTxParams struct {
|
||||||
EvmFee *EvmFee
|
EvmFee *EvmFee
|
||||||
SolanaFee *SolanaFee
|
SolanaFee *SolanaFee
|
||||||
Metrics metrics.Metrics
|
Metrics metrics.Metrics
|
||||||
|
P2pNetwork string
|
||||||
}
|
}
|
||||||
|
|
||||||
type EvmFee struct {
|
type EvmFee struct {
|
||||||
|
@ -52,9 +55,10 @@ func ProcessTargetTx(
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
repository *Repository,
|
repository *Repository,
|
||||||
params *ProcessTargetTxParams,
|
params *ProcessTargetTxParams,
|
||||||
|
notionalCache *notional.NotionalCache,
|
||||||
) error {
|
) error {
|
||||||
|
|
||||||
feeDetail := calculateFeeDetail(params, logger)
|
feeDetail := calculateFeeDetail(params, logger, notionalCache)
|
||||||
|
|
||||||
txHash := domain.NormalizeTxHashByChainId(params.ChainID, params.TxHash)
|
txHash := domain.NormalizeTxHashByChainId(params.ChainID, params.TxHash)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
@ -119,8 +123,10 @@ func checkTxShouldBeUpdated(ctx context.Context, tx *TargetTxUpdate, repository
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateFeeDetail(params *ProcessTargetTxParams, logger *zap.Logger) *FeeDetail {
|
func calculateFeeDetail(params *ProcessTargetTxParams, logger *zap.Logger, notionalCache *notional.NotionalCache) *FeeDetail {
|
||||||
|
|
||||||
// calculate tx fee for evm redeemed tx.
|
// calculate tx fee for evm redeemed tx.
|
||||||
|
var feeDetail *FeeDetail
|
||||||
if params.EvmFee != nil {
|
if params.EvmFee != nil {
|
||||||
fee, err := chains.EvmCalculateFee(params.ChainID, params.EvmFee.GasUsed, params.EvmFee.EffectiveGasPrice)
|
fee, err := chains.EvmCalculateFee(params.ChainID, params.EvmFee.GasUsed, params.EvmFee.EffectiveGasPrice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -133,27 +139,40 @@ func calculateFeeDetail(params *ProcessTargetTxParams, logger *zap.Logger) *FeeD
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if fee == "" {
|
if fee != nil {
|
||||||
return nil
|
feeDetail = &FeeDetail{
|
||||||
}
|
RawFee: map[string]string{
|
||||||
return &FeeDetail{
|
"gasUsed": params.EvmFee.GasUsed,
|
||||||
RawFee: map[string]string{
|
"effectiveGasPrice": params.EvmFee.EffectiveGasPrice,
|
||||||
"gasUsed": params.EvmFee.GasUsed,
|
},
|
||||||
"effectiveGasPrice": params.EvmFee.EffectiveGasPrice,
|
Fee: fee.String(),
|
||||||
},
|
}
|
||||||
Fee: fee,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// calculate tx fee for solana redeemed tx.
|
// calculate tx fee for solana redeemed tx.
|
||||||
if params.SolanaFee != nil {
|
if params.SolanaFee != nil {
|
||||||
fee := chains.SolanaCalculateFee(params.SolanaFee.Fee)
|
fee := chains.SolanaCalculateFee(params.SolanaFee.Fee)
|
||||||
return &FeeDetail{
|
feeDetail = &FeeDetail{
|
||||||
RawFee: map[string]string{
|
RawFee: map[string]string{
|
||||||
"fee": strconv.FormatUint(params.SolanaFee.Fee, 10),
|
"fee": strconv.FormatUint(params.SolanaFee.Fee, 10),
|
||||||
},
|
},
|
||||||
Fee: fee,
|
Fee: fee.String(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if feeDetail != nil && params.P2pNetwork == domain.P2pMainNet {
|
||||||
|
gasTokenPrice, errGasPrice := chains.GetGasTokenNotional(params.ChainID, notionalCache)
|
||||||
|
if errGasPrice != nil {
|
||||||
|
logger.Error("Failed to get gas price",
|
||||||
|
zap.Error(errGasPrice),
|
||||||
|
zap.String("chainId", params.ChainID.String()),
|
||||||
|
zap.String("txHash", params.TxHash),
|
||||||
|
)
|
||||||
|
return feeDetail
|
||||||
|
}
|
||||||
|
feeDetail.GasTokenNotional = gasTokenPrice.NotionalUsd.String()
|
||||||
|
feeDetail.FeeUSD = gasTokenPrice.NotionalUsd.Mul(decimal.RequireFromString(feeDetail.Fee)).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return feeDetail
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,8 @@ package vaa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/client/cache/notional"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/pool"
|
"github.com/wormhole-foundation/wormhole-explorer/common/pool"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/utils"
|
"github.com/wormhole-foundation/wormhole-explorer/common/utils"
|
||||||
|
@ -13,6 +11,8 @@ import (
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/txtracker/internal/metrics"
|
"github.com/wormhole-foundation/wormhole-explorer/txtracker/internal/metrics"
|
||||||
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Controller definition.
|
// Controller definition.
|
||||||
|
@ -24,10 +24,11 @@ type Controller struct {
|
||||||
repository *consumer.Repository
|
repository *consumer.Repository
|
||||||
metrics metrics.Metrics
|
metrics metrics.Metrics
|
||||||
p2pNetwork string
|
p2pNetwork string
|
||||||
|
notionalCache *notional.NotionalCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewController creates a Controller instance.
|
// NewController creates a Controller instance.
|
||||||
func NewController(rpcPool map[sdk.ChainID]*pool.Pool, wormchainRpcPool map[sdk.ChainID]*pool.Pool, vaaRepository *Repository, repository *consumer.Repository, p2pNetwork string, logger *zap.Logger) *Controller {
|
func NewController(rpcPool map[sdk.ChainID]*pool.Pool, wormchainRpcPool map[sdk.ChainID]*pool.Pool, vaaRepository *Repository, repository *consumer.Repository, p2pNetwork string, logger *zap.Logger, notionalCache *notional.NotionalCache) *Controller {
|
||||||
return &Controller{
|
return &Controller{
|
||||||
metrics: metrics.NewDummyMetrics(),
|
metrics: metrics.NewDummyMetrics(),
|
||||||
rpcPool: rpcPool,
|
rpcPool: rpcPool,
|
||||||
|
@ -35,7 +36,9 @@ func NewController(rpcPool map[sdk.ChainID]*pool.Pool, wormchainRpcPool map[sdk.
|
||||||
vaaRepository: vaaRepository,
|
vaaRepository: vaaRepository,
|
||||||
repository: repository,
|
repository: repository,
|
||||||
p2pNetwork: p2pNetwork,
|
p2pNetwork: p2pNetwork,
|
||||||
logger: logger}
|
logger: logger,
|
||||||
|
notionalCache: notionalCache,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) Process(ctx *fiber.Ctx) error {
|
func (c *Controller) Process(ctx *fiber.Ctx) error {
|
||||||
|
@ -70,9 +73,10 @@ func (c *Controller) Process(ctx *fiber.Ctx) error {
|
||||||
IsVaaSigned: true,
|
IsVaaSigned: true,
|
||||||
Metrics: c.metrics,
|
Metrics: c.metrics,
|
||||||
Overwrite: true,
|
Overwrite: true,
|
||||||
|
P2pNetwork: c.p2pNetwork,
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := consumer.ProcessSourceTx(ctx.Context(), c.logger, c.rpcPool, c.wormchainRpcPool, c.repository, p, c.p2pNetwork)
|
result, err := consumer.ProcessSourceTx(ctx.Context(), c.logger, c.rpcPool, c.wormchainRpcPool, c.repository, p, c.p2pNetwork, c.notionalCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -143,7 +147,7 @@ func (c *Controller) CreateTxHash(ctx *fiber.Ctx) error {
|
||||||
DisableDBUpsert: true,
|
DisableDBUpsert: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := consumer.ProcessSourceTx(ctx.Context(), c.logger, c.rpcPool, c.wormchainRpcPool, c.repository, p, c.p2pNetwork)
|
result, err := consumer.ProcessSourceTx(ctx.Context(), c.logger, c.rpcPool, c.wormchainRpcPool, c.repository, p, c.p2pNetwork, c.notionalCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue