wormhole-explorer/analytics/metric/mongo.go

107 lines
3.0 KiB
Go

package metric
import (
"context"
"fmt"
"time"
"github.com/shopspring/decimal"
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.uber.org/zap"
)
// TransferPriceDoc models a document in the `transferPrices` collection
type TransferPriceDoc struct {
// ID is the unique identifier of the VAA for which we are storing price information.
ID string `bson:"_id"`
// Timestamp is the timestamp of the VAA for which we are storing price information.
Timestamp time.Time `bson:"timestamp"`
// Symbol is the trading symbol of the token being transferred.
Symbol string `bson:"symbol"`
// SymbolPriceUsd is the price of the token in USD at the moment of the transfer.
SymbolPriceUsd string `bson:"price"`
// TokenAmount is the amount of the token being transferred.
TokenAmount string `bson:"tokenAmount"`
// UsdAmount is the value in USD of the token being transferred.
UsdAmount string `bson:"usdAmount"`
}
func upsertTransferPrices(
logger *zap.Logger,
vaa *sdk.VAA,
transferPrices *mongo.Collection,
tokenPriceFunc func(tokenID string, timestamp time.Time) (decimal.Decimal, error),
) error {
// Do not generate this metric for PythNet VAAs
if vaa.EmitterChain == sdk.ChainIDPythNet {
return nil
}
// Decode the VAA payload
payload, err := sdk.DecodeTransferPayloadHdr(vaa.Payload)
if err != nil {
return nil
}
// Get the token metadata
//
// This is complementary data about the token that is not present in the VAA itself.
tokenMeta, ok := domain.GetTokenByAddress(payload.OriginChain, payload.OriginAddress.String())
if !ok {
return nil
}
// Try to obtain the token notional value from the cache
notionalUSD, err := tokenPriceFunc(tokenMeta.GetTokenID(), vaa.Timestamp)
if err != nil {
logger.Warn("failed to obtain notional for this token",
zap.String("vaaId", vaa.MessageID()),
zap.String("tokenAddress", payload.OriginAddress.String()),
zap.Uint16("tokenChain", uint16(payload.OriginChain)),
zap.Any("tokenMetadata", tokenMeta),
zap.Error(err),
)
return nil
}
// Compute the amount with decimals
var exp int32
if tokenMeta.Decimals > 8 {
exp = 8
} else {
exp = int32(tokenMeta.Decimals)
}
tokenAmount := decimal.NewFromBigInt(payload.Amount, -exp)
// Compute the amount in USD
usdAmount := tokenAmount.Mul(notionalUSD)
// Upsert the `TransferPrices` collection
update := bson.M{
"$set": TransferPriceDoc{
ID: vaa.MessageID(),
Timestamp: vaa.Timestamp,
Symbol: tokenMeta.Symbol.String(),
SymbolPriceUsd: notionalUSD.Truncate(8).String(),
TokenAmount: tokenAmount.Truncate(8).String(),
UsdAmount: usdAmount.Truncate(8).String(),
},
}
_, err = transferPrices.UpdateByID(
context.Background(),
vaa.MessageID(),
update,
options.Update().SetUpsert(true),
)
if err != nil {
return fmt.Errorf("failed to update transfer price collection: %w", err)
}
return nil
}