wormhole-explorer/api/routes/wormscan/transactions/controller.go

210 lines
6.2 KiB
Go

package transactions
import (
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/shopspring/decimal"
"github.com/wormhole-foundation/wormhole-explorer/api/handlers/transactions"
"github.com/wormhole-foundation/wormhole-explorer/api/middleware"
"go.uber.org/zap"
)
// Controller is the controller for the transactions resource.
type Controller struct {
srv *transactions.Service
logger *zap.Logger
}
// NewController create a new controler.
func NewController(transactionsService *transactions.Service, logger *zap.Logger) *Controller {
return &Controller{
srv: transactionsService,
logger: logger.With(zap.String("module", "TransactionsController")),
}
}
// GetLastTransactions godoc
// @Description Returns the number of transactions [vaa] by a defined time span and sample rate.
// @Tags Wormscan
// @ID get-last-transactions
// @Param timeSpan query string false "Time Span, default: 1d, supported values: [1d, 1w, 1mo]"
// @Param sampleRate query string false "Sample Rate, default: 1h, supported values: [1h, 1d]"
// @Success 200 {object} []transactions.TransactionCountResult
// @Failure 400
// @Failure 500
// @Router /api/v1/last-txs [get]
func (c *Controller) GetLastTransactions(ctx *fiber.Ctx) error {
timeSpan, err := middleware.ExtractTimeSpan(ctx, c.logger)
if err != nil {
return err
}
sampleRate, err := middleware.ExtractSampleRate(ctx, c.logger)
if err != nil {
return err
}
q := &transactions.TransactionCountQuery{
TimeSpan: timeSpan,
SampleRate: sampleRate,
}
// Get transaction count.
lastTrx, err := c.srv.GetTransactionCount(ctx.Context(), q)
if err != nil {
return err
}
return ctx.JSON(lastTrx)
}
// GetScorecards godoc
// @Description Returns a list of KPIs for Wormhole.
// @Tags Wormscan
// @ID get-scorecards
// @Success 200 {object} ScorecardsResponse
// @Failure 500
// @Router /api/v1/scorecards [get]
func (c *Controller) GetScorecards(ctx *fiber.Ctx) error {
// Query indicators from the database
scorecards, err := c.srv.GetScorecards(ctx.Context())
if err != nil {
c.logger.Error("failed to get scorecards", zap.Error(err))
return err
}
// Convert indicators to the response model
response := ScorecardsResponse{
TotalTxCount: scorecards.TotalTxCount,
TxCount24h: scorecards.TxCount24h,
Volume24h: scorecards.Volume24h,
}
return ctx.JSON(response)
}
// GetChainActivity godoc
// @Description Returns a list of tx by source chain and destination chain.
// @Tags Wormscan
// @ID x-chain-activity
// @Param start_time query string false "Star time (format: ISO-8601)."
// @Param end_time query string false "End time (format: ISO-8601)."
// @Param by query string false "Renders the results as notional or tx-count (default is notional)."
// @Param apps query string false "List of apps separated by comma (default is all apps)."
// @Success 200 {object} transactions.ChainActivity
// @Failure 400
// @Failure 500
// @Router /api/v1/x-chain-activity [get]
func (c *Controller) GetChainActivity(ctx *fiber.Ctx) error {
startTime, endTime, err := middleware.ExtractTimeRange(ctx)
if err != nil {
return err
}
apps, err := middleware.ExtractApps(ctx)
if err != nil {
return err
}
isNotional, err := middleware.ExtractIsNotional(ctx)
if err != nil {
return err
}
q := &transactions.ChainActivityQuery{
Start: startTime,
End: endTime,
AppIDs: apps,
IsNotional: isNotional,
}
// Get the chain activity.
activity, err := c.srv.GetChainActivity(ctx.Context(), q)
if err != nil {
c.logger.Error("Error getting chain activity", zap.Error(err))
return err
}
// Convert the result to the expected format.
txs, err := c.createChainActivityResponse(activity)
if err != nil {
return err
}
return ctx.JSON(ChainActivity{Txs: txs})
}
func (c *Controller) createChainActivityResponse(activity []transactions.ChainActivityResult) ([]Tx, error) {
txByChainID := make(map[int]*Tx)
total := decimal.Zero
for _, item := range activity {
chainSourceID, err := strconv.Atoi(item.ChainSourceID)
if err != nil {
c.logger.Error("Error during conversion of chainSourceId", zap.Error(err))
return nil, err
}
t, ok := txByChainID[chainSourceID]
if !ok {
destinations := make([]Destination, 0)
t = &Tx{Chain: chainSourceID, Volume: decimal.Zero, Percentage: 0, Destinations: destinations}
}
chainDestinationID, err := strconv.Atoi(item.ChainDestinationID)
if err != nil {
c.logger.Error("Error during conversion of chainDestinationId", zap.Error(err))
return nil, err
}
volume, err := decimal.NewFromString(strconv.FormatUint(item.Volume, 10))
if err != nil {
c.logger.Error("Error during conversion of volume to decimal", zap.Error(err))
return nil, err
}
destination := Destination{Chain: chainDestinationID, Volume: volume, Percentage: 0}
t.Destinations = append(t.Destinations, destination)
t.Volume = t.Volume.Add(volume)
txByChainID[chainSourceID] = t
total = total.Add(volume)
}
txs := make([]Tx, 0)
oneHundred := decimal.NewFromInt(100)
for _, item := range txByChainID {
if total.GreaterThan(decimal.Zero) {
percentage, _ := item.Volume.Div(total).Mul(oneHundred).Float64()
item.Percentage = percentage
}
for i, destination := range item.Destinations {
if item.Volume.GreaterThan(decimal.Zero) {
percentage, _ := destination.Volume.Div(item.Volume).Mul(oneHundred).Float64()
item.Destinations[i].Percentage = percentage
}
}
txs = append(txs, *item)
}
return txs, nil
}
// FindGlobalTransactionByID godoc
// @Description Find a global transaction by ID.
// @Tags Wormscan
// @ID find-global-transaction-by-id
// @Param chain_id path integer true "id of the blockchain"
// @Param emitter path string true "address of the emitter"
// @Param seq path integer true "sequence of the VAA"
// @Success 200 {object} Tx
// @Failure 400
// @Failure 500
// @Router /api/v1/global-tx/{chain_id}/{emitter}/{seq} [get]
func (c *Controller) FindGlobalTransactionByID(ctx *fiber.Ctx) error {
chainID, emitter, seq, err := middleware.ExtractVAAParams(ctx, c.logger)
if err != nil {
return err
}
globalTransaction, err := c.srv.FindGlobalTransactionByID(ctx.Context(), chainID, emitter, strconv.FormatUint(seq, 10))
if err != nil {
return err
}
return ctx.JSON(globalTransaction)
}